diff --git a/wizard-work/forge/_triage_dump_pending_refactored.py b/wizard-work/forge/_triage_dump_pending_refactored.py index 6177918..2c32b0d 100644 --- a/wizard-work/forge/_triage_dump_pending_refactored.py +++ b/wizard-work/forge/_triage_dump_pending_refactored.py @@ -17,8 +17,12 @@ Smells fixed in this refactor: * **Hardcoded path at module scope** -- the JSON path was a bare module-level assignment (``path = "/root/troupe-standup/..."``). It is promoted to the named constant ``DEFAULT_PATH`` and made the ``path`` parameter of ``main``, - so callers (including tests) can override it without editing source. The - default is unchanged, so output is byte-identical when run with no args. + so callers (including tests) can override it without editing source. + ``DEFAULT_PATH`` is derived from this file's location (walked up to the repo + root + ``inbox/seed-...json``) rather than a host-absolute literal, so a + clone grades its own inbox instead of this host's (Q86). On this host the + resolution is identical to the old literal, so output is byte-identical + when run with no args. * **No function extraction** -- the three inline print blocks (summary header, decided iteration, pending iteration) are extracted into @@ -36,10 +40,17 @@ Output is byte-identical to the original on the current inbox seed from __future__ import annotations import json +import os import sys from typing import Any -DEFAULT_PATH = "/root/troupe-standup/inbox/seed-2026-09-26.json" +# Resolve the seed from THIS file's location so the probe grades the tree it +# lives in, not this host's inbox (Q86 portability gap). wizard-work/forge/ is +# two levels under the repo root, which holds inbox/. +_HERE = os.path.dirname(os.path.abspath(__file__)) +DEFAULT_PATH = os.path.join( + os.path.dirname(os.path.dirname(_HERE)), "inbox", "seed-2026-09-26.json" +) def load_data(path: str) -> dict[str, Any]: diff --git a/wizard-work/forge/_triage_probe_refactored.py b/wizard-work/forge/_triage_probe_refactored.py index d8f04e8..346c43d 100644 --- a/wizard-work/forge/_triage_probe_refactored.py +++ b/wizard-work/forge/_triage_probe_refactored.py @@ -24,8 +24,12 @@ Smells fixed in this refactor (behavior-preserving on the probe itself): * **Hardcoded path at module scope.** The JSON path was a bare module-level assignment with no override. It is promoted to the named constant ``DEFAULT_PATH`` and made the ``path`` parameter of ``main``, so callers - (including tests) can override it without editing source. The default is - unchanged, so output is byte-identical when run with no args. + (including tests) can override it without editing source. ``DEFAULT_PATH`` + is derived from this file's location (walked up to the repo root + + ``inbox/seed-...json``) rather than a host-absolute literal, so a clone + grades its own inbox instead of this host's (Q86). On this host the + resolution is identical to the old literal, so output is byte-identical + when run with no args. * **No function extraction / repeated ``d.get``.** The inline print blocks (header, decided iteration, pending iteration) are extracted into the pure @@ -39,8 +43,15 @@ Output is byte-identical to the original on the current inbox seed ``49d9b87be277211a29c6ba6f685e8516df0f0235c6fa332f52a9d3f00b779f34``. """ import json +import os -DEFAULT_PATH = "/root/troupe-standup/inbox/seed-2026-09-26.json" +# Resolve the seed from THIS file's location so the probe grades the tree it +# lives in, not this host's inbox (Q86 portability gap). wizard-work/forge/ is +# two levels under the repo root, which holds inbox/. +_HERE = os.path.dirname(os.path.abspath(__file__)) +DEFAULT_PATH = os.path.join( + os.path.dirname(os.path.dirname(_HERE)), "inbox", "seed-2026-09-26.json" +) def load_data(path): diff --git a/wizard-work/forge/verify_triage_dump_pending_refactor.py b/wizard-work/forge/verify_triage_dump_pending_refactor.py index 2689274..2f3c557 100644 --- a/wizard-work/forge/verify_triage_dump_pending_refactor.py +++ b/wizard-work/forge/verify_triage_dump_pending_refactor.py @@ -25,6 +25,18 @@ probe itself (same JSON input, same output). Checks: prints the custom file's contents. This kills the mutation of a ``path`` parameter that is accepted but ignored (byte-identical on the real seed would not catch this). + 10. No import-time I/O (M6 killer): a static AST walk of the top-level + statements plus a runtime ``builtins.open`` spy both require zero calls. + Mutant M6 (``_bootstrap = json.load(open(DEFAULT_PATH))`` at module scope, + no print) survives check 5 (rc 0, zero output) by being *silent*, not + clean -- the read happened but was invisible to the silence test. + 11. M6 is detected by check 10's io-detector (non-vacuity): the detector fires + on the measured mutant applied to a temp copy, so it is sensitive rather + than theater. + 12. The seed path is tree-portable: no host-absolute literal in the refactored + source, and ``DEFAULT_PATH`` resolves inside the tree. Closes the Q86 + "a clone grades the host's inbox" gap -- on this host the resolution is + identical to the old literal, so check 2 stays byte-identical. """ from __future__ import annotations @@ -44,6 +56,16 @@ WORK_DIR = os.path.dirname(os.path.abspath(__file__)) ORIGINAL = "_triage_dump_pending.py" REFACTORED = "_triage_dump_pending_refactored.py" MOD_NAME = REFACTORED[:-3] # strip .py +SEED_FILENAME = "seed-2026-09-26.json" +# The original probe's literal -- used only to assert the ORIGINAL still carries +# the host-absolute smell (clone-independent, unlike comparing against a resolved +# real-seed path). +HOST_SEED_LITERAL = "/root/troupe-standup/inbox/seed-2026-09-26.json" +# The seed is derived from THIS file's location (walked up to the repo root), +# not a host-absolute literal, so a clone grades its own inbox (Q86). On this +# host the resolution is identical to the old literal -> check 2 stays green. +REAL_SEED_PATH = os.path.join( + os.path.dirname(os.path.dirname(WORK_DIR)), "inbox", SEED_FILENAME) def _run(*args: str) -> subprocess.CompletedProcess: @@ -251,17 +273,38 @@ def check_main_path_parameter() -> bool: return True +def _original_hardcodes_absolute_seed(src: str) -> bool: + """True if the original assigns an absolute path literal to ``path`` at + module scope -- the host-absolute smell the refactor removes. + + AST-based so the test is clone-independent: the original's literal is + host-absolute by design, which would break a naive ``REAL_SEED_PATH in + code`` test on any checkout whose REAL_SEED_PATH is tree-relative (i.e. + everywhere, after this fix). + """ + tree = ast.parse(src) + for stmt in tree.body: + if (isinstance(stmt, ast.Assign) + and isinstance(stmt.value, ast.Constant) + and isinstance(stmt.value.value, str) + and stmt.value.value.startswith("/")): + for t in stmt.targets: + if isinstance(t, ast.Name) and t.id == "path": + return True + return False + + def check_original_still_smells() -> bool: """The original must still carry the smells we removed (non-vacuous).""" src = _read(ORIGINAL) - code = _code_only(src) problems = [] # The original must LACK a __main__ guard (that is the smell we fixed). if '__name__ == "__main__"' in src: problems.append("original already has __main__ guard (refactor not needed)") # The hardcoded path must still be at module scope in the original. - if "/root/troupe-standup/inbox/seed-2026-09-26.json" not in code: - problems.append("hardcoded path missing from original (expected)") + if not _original_hardcodes_absolute_seed(src): + problems.append("module-scope absolute `path = ` missing from " + "original (expected)") # The original must execute the probe at import time: module-level for-loop. tree = ast.parse(src) has_module_level_for = any(isinstance(n, ast.For) for n in tree.body) @@ -315,7 +358,7 @@ def check_path_override_honoured() -> bool: print(f"FAIL: main(path=custom) did not read the custom file " f"(rc={rc}, output={combined!r})") return False - if "/root/troupe-standup/inbox/seed-2026-09-26.json" in combined: + if HOST_SEED_LITERAL in combined: print("FAIL: main(path=custom) printed data from DEFAULT_PATH, not " "the custom path") return False @@ -327,6 +370,158 @@ def check_path_override_honoured() -> bool: shutil.rmtree(tmp_dir, ignore_errors=True) +# ---------------------------------------------------------------------- +# Q86: the refactor-lane harness proved SILENCE at import, not ABSENCE of I/O, +# and its seed path was host-absolute. The two fixes below close both gaps. +# +# A Call to any of these at MODULE SCOPE runs while the module is imported -- +# the M6 mutant (`_bootstrap = json.load(open(DEFAULT_PATH))`, no print) hides +# from check 5 because it emits no stdout, so it needs its own detector. +_IO_CALLS = frozenset({ + "open", "json.load", "json.loads", "pickle.load", "pickle.loads", + "csv.reader", "csv.DictReader", "urllib.request.urlopen", "codecs.open", +}) + + +def _call_name(call: ast.Call): + """Dotted name of a Call's function (``json.load``, ``open``, ...), or + None for a callable expression this detector does not model.""" + parts = [] + cur = call.func + while isinstance(cur, ast.Attribute): + parts.append(cur.attr) + cur = cur.value + if isinstance(cur, ast.Name): + parts.append(cur.id) + return ".".join(reversed(parts)) + return None + + +def _calls_at_module_scope(src: str) -> list: + """``[(lineno, name)]`` for io-reading Call nodes at module scope. + + A Call counts as "module scope" only when it is NOT nested inside a + FunctionDef / AsyncFunctionDef / ClassDef body -- those run lazily, at call + time, and are exactly the I/O the refactor is allowed to keep (it lives + under ``main()``). The ``if __name__ == "__main__"`` guard's ``main()`` + call resolves to ``main`` (not in the io set), so it is correctly ignored. + """ + tree = ast.parse(src) + found: list = [] + + def scan(node): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + return # do not descend -- calls inside a def body are lazy + if isinstance(node, ast.Call): + name = _call_name(node) + if name in _IO_CALLS: + found.append((node.lineno, name)) + for child in ast.iter_child_nodes(node): + scan(child) + + for stmt in tree.body: + scan(stmt) + return found + + +def check_no_import_time_io() -> bool: + """Check 5 proves the import is SILENT, not that no I/O happens. M6 + (`_bootstrap = json.load(open(DEFAULT_PATH))` at module scope, no print) + survives check 5 (rc 0, zero output) while reading the seed at import. + Two independent detectors, both must read zero on the clean module: + (a) static: AST-walk the top-level statements for an open/json.load call; + (b) dynamic: patch builtins.open to a recorder, re-import the module, and + require zero calls. The import machinery reads .py source via + _io.FileIO (not builtins.open), so this does NOT false-positive on a + clean module -- measured separately (0 calls on a fresh import).""" + static = _calls_at_module_scope(_read(REFACTORED)) + if static: + print("FAIL: static io-call(s) at module scope: %s" % static) + return False + import builtins + calls: list = [] + real_open = builtins.open + + def _spy_open(*args, **kwargs): + calls.append(args[0] if args else kwargs.get("file")) + raise RuntimeError("__check_no_import_time_io__ import-time open()") + + builtins.open = _spy_open + try: + _mod, exc, _out, _err = _safe_import() + finally: + builtins.open = real_open + if exc is not None: + # A RuntimeError carrying our marker is the very failure we reject; + # any other exception is a real import error and is ungradable here. + if isinstance(exc, RuntimeError) and "__check_no_import_time_io__" in str(exc): + print("FAIL: runtime import-time open() on %r (rc 2)" % calls) + return False + print("FAIL: import raised %s: %r (stdout=%r stderr=%r)" + % (type(exc).__name__, exc, _out, _err)) + return False + if calls: + print("FAIL: import made %d open() call(s): %r" % (len(calls), calls)) + return False + print("OK: no import-time I/O (0 module-scope io calls; 0 runtime open calls)") + return True + + +def check_m6_mutant_killed() -> bool: + """Non-vacuity for check 10: a detector that matches nothing is theater. + Apply the measured M6 mutation -- a silent module-scope + ``json.load(open(DEFAULT_PATH))`` inserted before the first def -- to a temp + copy of the source and confirm the static io-detector fires on it. The + clean module has zero such calls (check 10); the mutant has two, so the + detector is sensitive rather than vacuous. (M6 is the mutant measured for + Q86: it returns 10/10 under the old check 5.)""" + src = _read(REFACTORED) + anchor = "def load_data(path: str) -> dict[str, Any]:" + m6_expr = "_bootstrap = json.load(open(DEFAULT_PATH))\n" + mutated = src.replace(anchor, m6_expr + anchor, 1) + if m6_expr not in mutated: + # guard against the insertion anchor drifting in the source + print("FAIL: M6 mutation could not be applied (anchor %r not found)" % anchor) + return False + sites = _calls_at_module_scope(mutated) + if not sites: + print("FAIL: M6 mutant NOT detected by the io-detector (check 10 is vacuous?)") + return False + print("OK: M6 mutant detected at module scope: %s" % sites) + return True + + +def check_seed_path_portable() -> bool: + """Q86 gap #2: the seed path must derive from the tree, not a host-absolute + literal, so a clone grades its own inbox. Measured against two claims: + (a) the refactored source holds no host-absolute seed literal; + (b) importing the module, its DEFAULT_PATH resolves to a path INSIDE this + file's tree, and that resolution matches the harness's REAL_SEED_PATH. + A clone with its inbox moved aside now grades the clone (and fails on a + missing seed) instead of silently grading /root/... on the host.""" + src = _read(REFACTORED) + if HOST_SEED_LITERAL in src: + print("FAIL: refactored still hardcodes a host-absolute seed path") + return False + mod, exc, out, err = _safe_import() + if exc is not None or mod is None: + print("FAIL: import raised %r (stdout=%r stderr=%r)" % (exc, out, err)) + return False + resolved = os.path.realpath(mod.DEFAULT_PATH) + repo_root = os.path.realpath(os.path.dirname(os.path.dirname(WORK_DIR))) + if not (resolved == repo_root + or resolved.startswith(repo_root + os.sep)): + print("FAIL: DEFAULT_PATH %r is outside the tree root %r" + % (resolved, repo_root)) + return False + if os.path.realpath(REAL_SEED_PATH) != resolved: + print("FAIL: harness REAL_SEED_PATH (%r) != module DEFAULT_PATH (%r)" + % (os.path.realpath(REAL_SEED_PATH), resolved)) + return False + print("OK: seed path is tree-portable (resolves inside tree): %s" % resolved) + return True + + def main() -> None: checks = [ check_py_compile, @@ -339,6 +534,9 @@ def main() -> None: check_main_path_parameter, check_original_still_smells, check_path_override_honoured, + check_no_import_time_io, + check_m6_mutant_killed, + check_seed_path_portable, ] results = [fn() for fn in checks] passed = sum(results) diff --git a/wizard-work/forge/verify_triage_probe_refactor.py b/wizard-work/forge/verify_triage_probe_refactor.py index 9aa3c06..4acce3b 100644 --- a/wizard-work/forge/verify_triage_probe_refactor.py +++ b/wizard-work/forge/verify_triage_probe_refactor.py @@ -36,6 +36,18 @@ Checks: prints the custom file's contents. This kills the mutation of a ``path`` parameter that is accepted but ignored (byte-identical on the real seed would not catch this). + 12. No import-time I/O (M6 killer): a static AST walk of the top-level + statements plus a runtime ``builtins.open`` spy both require zero calls. + Mutant M6 (``_bootstrap = json.load(open(DEFAULT_PATH))`` at module scope, + no print) survives check 5 (rc 0, zero output) by being *silent*, not + clean -- the read happened but was invisible to the silence test. + 13. M6 is detected by check 12's io-detector (non-vacuity): the detector fires + on the measured mutant applied to a temp copy, so it is sensitive rather + than theater. + 14. The seed path is tree-portable: no host-absolute literal in the refactored + source, and ``DEFAULT_PATH`` resolves inside the tree. Closes the Q86 + "a clone grades the host's inbox" gap -- on this host the resolution is + identical to the old literal, so check 2 stays byte-identical. """ from __future__ import annotations @@ -56,7 +68,16 @@ WORK_DIR = os.path.dirname(os.path.abspath(__file__)) ORIGINAL = "_triage_probe.py" REFACTORED = "_triage_probe_refactored.py" MOD_NAME = REFACTORED[:-3] # strip .py -> _triage_probe_refactored -REAL_SEED_PATH = "/root/troupe-standup/inbox/seed-2026-09-26.json" +SEED_FILENAME = "seed-2026-09-26.json" +# The original probe's literal -- used only to assert the ORIGINAL still carries +# the host-absolute smell (clone-independent, unlike comparing against a resolved +# real-seed path). +HOST_SEED_LITERAL = "/root/troupe-standup/inbox/seed-2026-09-26.json" +# The seed is derived from THIS file's location (walked up to the repo root), +# not a host-absolute literal, so a clone grades its own inbox (Q86). On this +# host the resolution is identical to the old literal -> check 2 stays green. +REAL_SEED_PATH = os.path.join( + os.path.dirname(os.path.dirname(WORK_DIR)), "inbox", SEED_FILENAME) def _run(*args: str) -> subprocess.CompletedProcess: @@ -279,15 +300,36 @@ def check_dead_sys_import_removed() -> bool: return True +def _original_hardcodes_absolute_seed(src: str) -> bool: + """True if the original assigns an absolute path literal to ``path`` at + module scope -- the host-absolute smell the refactor removes. + + AST-based so the test is clone-independent: the original's literal is + host-absolute by design (``/root/.../inbox/seed-....json``), which would + break a naive ``REAL_SEED_PATH in code`` test on any checkout whose + REAL_SEED_PATH is tree-relative (i.e. everywhere, after this fix). + """ + tree = ast.parse(src) + for stmt in tree.body: + if (isinstance(stmt, ast.Assign) + and isinstance(stmt.value, ast.Constant) + and isinstance(stmt.value.value, str) + and stmt.value.value.startswith("/")): + for t in stmt.targets: + if isinstance(t, ast.Name) and t.id == "path": + return True + return False + + def check_original_still_smells() -> bool: """The original must still carry the smells we removed (non-vacuous).""" src = _read(ORIGINAL) - code = _code_only(src) problems = [] if '__name__ == "__main__"' in src: problems.append("original already has __main__ guard (refactor not needed)") - if REAL_SEED_PATH not in code: - problems.append("hardcoded path missing from original (expected)") + if not _original_hardcodes_absolute_seed(src): + problems.append("module-scope absolute `path = ` missing from " + "original (expected)") tree = ast.parse(src) has_module_level_for = any(isinstance(n, ast.For) for n in tree.body) if not has_module_level_for: @@ -352,6 +394,158 @@ def check_path_override_honoured() -> bool: shutil.rmtree(tmp_dir, ignore_errors=True) +# ---------------------------------------------------------------------- +# Q86: the refactor-lane harness proved SILENCE at import, not ABSENCE of I/O, +# and its seed path was host-absolute. The two fixes below close both gaps. +# +# A Call to any of these at MODULE SCOPE runs while the module is imported -- +# the M6 mutant (`_bootstrap = json.load(open(DEFAULT_PATH))`, no print) hides +# from check 5 because it emits no stdout, so it needs its own detector. +_IO_CALLS = frozenset({ + "open", "json.load", "json.loads", "pickle.load", "pickle.loads", + "csv.reader", "csv.DictReader", "urllib.request.urlopen", "codecs.open", +}) + + +def _call_name(call: ast.Call): + """Dotted name of a Call's function (``json.load``, ``open``, ...), or + None for a callable expression this detector does not model.""" + parts = [] + cur = call.func + while isinstance(cur, ast.Attribute): + parts.append(cur.attr) + cur = cur.value + if isinstance(cur, ast.Name): + parts.append(cur.id) + return ".".join(reversed(parts)) + return None + + +def _calls_at_module_scope(src: str) -> list: + """``[(lineno, name)]`` for io-reading Call nodes at module scope. + + A Call counts as "module scope" only when it is NOT nested inside a + FunctionDef / AsyncFunctionDef / ClassDef body -- those run lazily, at call + time, and are exactly the I/O the refactor is allowed to keep (it lives + under ``main()``). The ``if __name__ == "__main__"`` guard's ``main()`` + call resolves to ``main`` (not in the io set), so it is correctly ignored. + """ + tree = ast.parse(src) + found: list = [] + + def scan(node): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + return # do not descend -- calls inside a def body are lazy + if isinstance(node, ast.Call): + name = _call_name(node) + if name in _IO_CALLS: + found.append((node.lineno, name)) + for child in ast.iter_child_nodes(node): + scan(child) + + for stmt in tree.body: + scan(stmt) + return found + + +def check_no_import_time_io() -> bool: + """Check 5 proves the import is SILENT, not that no I/O happens. M6 + (`_bootstrap = json.load(open(DEFAULT_PATH))` at module scope, no print) + survives check 5 (rc 0, zero output) while reading the seed at import. + Two independent detectors, both must read zero on the clean module: + (a) static: AST-walk the top-level statements for an open/json.load call; + (b) dynamic: patch builtins.open to a recorder, re-import the module, and + require zero calls. The import machinery reads .py source via + _io.FileIO (not builtins.open), so this does NOT false-positive on a + clean module -- measured separately (0 calls on a fresh import).""" + static = _calls_at_module_scope(_read(REFACTORED)) + if static: + print("FAIL: static io-call(s) at module scope: %s" % static) + return False + import builtins + calls: list = [] + real_open = builtins.open + + def _spy_open(*args, **kwargs): + calls.append(args[0] if args else kwargs.get("file")) + raise RuntimeError("__check_no_import_time_io__ import-time open()") + + builtins.open = _spy_open + try: + _mod, exc, _out, _err = _safe_import() + finally: + builtins.open = real_open + if exc is not None: + # A RuntimeError carrying our marker is the very failure we reject; + # any other exception is a real import error and is ungradable here. + if isinstance(exc, RuntimeError) and "__check_no_import_time_io__" in str(exc): + print("FAIL: runtime import-time open() on %r (rc 2)" % calls) + return False + print("FAIL: import raised %s: %r (stdout=%r stderr=%r)" + % (type(exc).__name__, exc, _out, _err)) + return False + if calls: + print("FAIL: import made %d open() call(s): %r" % (len(calls), calls)) + return False + print("OK: no import-time I/O (0 module-scope io calls; 0 runtime open calls)") + return True + + +def check_m6_mutant_killed() -> bool: + """Non-vacuity for check 12: a detector that matches nothing is theater. + Apply the measured M6 mutation -- a silent module-scope + ``json.load(open(DEFAULT_PATH))`` inserted before the first def -- to a temp + copy of the source and confirm the static io-detector fires on it. The + clean module has zero such calls (check 12); the mutant has two, so the + detector is sensitive rather than vacuous. (M6 is the mutant measured for + Q86: it returns 11/11 under the old check 5.)""" + src = _read(REFACTORED) + anchor = "def load_data(path):" + m6_expr = "_bootstrap = json.load(open(DEFAULT_PATH))\n" + mutated = src.replace(anchor, m6_expr + anchor, 1) + if m6_expr not in mutated: + # guard against the insertion anchor drifting in the source + print("FAIL: M6 mutation could not be applied (anchor %r not found)" % anchor) + return False + sites = _calls_at_module_scope(mutated) + if not sites: + print("FAIL: M6 mutant NOT detected by the io-detector (check 12 is vacuous?)") + return False + print("OK: M6 mutant detected at module scope: %s" % sites) + return True + + +def check_seed_path_portable() -> bool: + """Q86 gap #2: the seed path must derive from the tree, not a host-absolute + literal, so a clone grades its own inbox. Measured against two claims: + (a) the refactored source holds no host-absolute seed literal; + (b) importing the module, its DEFAULT_PATH resolves to a path INSIDE this + file's tree, and that resolution matches the harness's REAL_SEED_PATH. + A clone with its inbox moved aside now grades the clone (and fails on a + missing seed) instead of silently grading /root/... on the host.""" + src = _read(REFACTORED) + if HOST_SEED_LITERAL in src: + print("FAIL: refactored still hardcodes a host-absolute seed path") + return False + mod, exc, out, err = _safe_import() + if exc is not None or mod is None: + print("FAIL: import raised %r (stdout=%r stderr=%r)" % (exc, out, err)) + return False + resolved = os.path.realpath(mod.DEFAULT_PATH) + repo_root = os.path.realpath(os.path.dirname(os.path.dirname(WORK_DIR))) + if not (resolved == repo_root + or resolved.startswith(repo_root + os.sep)): + print("FAIL: DEFAULT_PATH %r is outside the tree root %r" + % (resolved, repo_root)) + return False + if os.path.realpath(REAL_SEED_PATH) != resolved: + print("FAIL: harness REAL_SEED_PATH (%r) != module DEFAULT_PATH (%r)" + % (os.path.realpath(REAL_SEED_PATH), resolved)) + return False + print("OK: seed path is tree-portable (resolves inside tree): %s" % resolved) + return True + + def main() -> None: checks = [ check_py_compile, @@ -365,6 +559,9 @@ def main() -> None: check_dead_sys_import_removed, check_original_still_smells, check_path_override_honoured, + check_no_import_time_io, + check_m6_mutant_killed, + check_seed_path_portable, ] results = [fn() for fn in checks] passed = sum(results)