Files
theclaymethod__unslop/plans/001-python-floor.md
Clayton Kim db7cc058c3 Add the improve-campaign plan set and execution record
15 plans written against 217c218, executed by dispatched agents in isolated
worktrees, reviewed and approved individually, integrated and battery-proven
on advisor/integration. plans/README.md carries the full status ledger, the
findings surfaced during execution, and the one known follow-up (in-process
stdin shim needs a .buffer for 011's full speed win).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 07:19:06 -07:00

8.6 KiB

Plan 001: Make the advertised Python 3.8+ floor true, and test it in CI

Executor instructions: Follow this plan step by step. Run every verification command and confirm the expected result before moving to the next step. If anything in the "STOP conditions" section occurs, stop and report — do not improvise. When done, update the status row for this plan in plans/README.md.

Drift check (run first): git diff --stat 217c218..HEAD -- scripts/ evals/ README.md .github/workflows/evals.yml If any in-scope file changed since this plan was written, compare the "Current state" excerpts against the live code before proceeding; on a mismatch, treat it as a STOP condition.

Status

  • Priority: P1
  • Effort: S
  • Risk: LOW
  • Depends on: none
  • Category: bug
  • Planned at: commit 217c218, 2026-07-06

Why this matters

README.md promises "Python 3.8+ and the standard library" (lines 48 and 455), but 17 Python files use PEP 604 (str | None) and PEP 585 (dict[str, ...]) annotations in positions evaluated at import time, with no from __future__ import annotations. On Python 3.8/3.9 the flagship scanner scripts/banned_phrase_scan.py raises TypeError at import — the primary entry point is broken on two advertised platforms. CI pins only Python 3.11, so no gate can ever catch this. This plan makes the floor claim true and adds a CI leg at the floor so it stays true.

Current state

  • scripts/banned_phrase_scan.py:82 — module-level evaluated annotation:
    BANNED_PHRASES: dict[str, dict[str, str | None]] = {
    
    and there is no from __future__ import annotations anywhere in the file.
  • The full list of files with evaluated modern annotations and no future-import (verified at 217c218): scripts/banned_phrase_scan.py, scripts/check_packs.py, scripts/check_suggestions.py, scripts/diff_check.py, scripts/extract_constraints.py, scripts/readability_metrics.py, scripts/silhouette_scan.py, scripts/structure_scan.py, scripts/suggest.py, scripts/validate_preservation.py, scripts/voice_score.py, scripts/wiki_sync.py, evals/build_shared_benchmark.py, evals/check_commands.py, evals/check_gates_doc.py, evals/check_silhouette.py, evals/run_local.py.
  • The repo's own convention already exists: scripts/harvest_samples.py, scripts/harvest_classify.py, scripts/contribute.py, scripts/calibrate_pairs.py, scripts/calibrate_score.py, and evals/check_contrib.py all begin with from __future__ import annotations directly after the module docstring. Match that placement.
  • .github/workflows/evals.yml:15 — single CI leg, python-version: "3.11".
  • README.md:267 and README.md:374 say "439 deterministic cases" / "439 cases"; the suite actually holds 440 target=="script" rows (439 pass + 1 documented xfail, FP-06). README.md:294 ("439 pass, 1 xfail") is already correct. Fix the two imprecise sites while this plan is in the README anyway.

Commands you will need

Purpose Command Expected on success
Full suite python3 evals/run_adversarial.py exit 0; final line PASS 439 XFAIL 1 ... FAIL 0
Syntax check python3 -m py_compile scripts/*.py evals/*.py exit 0, silent
Scanner self-check on README python3 scripts/banned_phrase_scan.py README.md exit 0, "total_violations": 0

Scope

In scope (the only files you should modify):

  • The 17 files listed above (one-line insertion each)
  • .github/workflows/evals.yml
  • README.md (two phrases only)

Out of scope (do NOT touch):

  • Any annotation itself — do not rewrite str | None to Optional[str]; the future-import fixes evaluation without churn.
  • docs/PRODUCT.md and other references — no floor claim appears there as of 217c218 (verify with grep -rn "3\.8" docs/ references/; if a claim appears, update it identically).
  • Anything in evals/adversarial-evals.json.

Git workflow

  • Branch: advisor/001-python-floor off main
  • One commit; message style matches repo (git log --oneline -5 shows sentence-case imperative, e.g. "Add codex-jsonl harvester adapter…")
  • Do NOT push or open a PR unless the operator instructed it.

Steps

Step 1: Add the future-import to the 17 files

In each listed file, insert from __future__ import annotations as the first import, immediately after the module docstring (before any other import), matching scripts/harvest_samples.py's layout exactly.

Verify: grep -L "from __future__ import annotations" scripts/*.py evals/*.py | xargs grep -l ": *\(dict\|list\|set\|tuple\)\[\|| None" → no output (every file that still lacks the import has no evaluated modern annotation).

Step 2: Prove 3.8-compatible imports mechanically

Python 3.8 may not be installed locally; simulate the failure class instead: for each of the 17 files, confirm the module compiles and that no runtime subscripted generic remains outside annotation position:

Verify: python3 -m py_compile scripts/*.py evals/*.py → exit 0 Verify: grep -n "get_type_hints" scripts/*.py evals/*.py → no output (nothing resolves annotations at runtime, so deferral is safe).

Step 3: Add a floor leg to CI

In .github/workflows/evals.yml, convert the job to a matrix over python-version: ["3.8", "3.11"]. Keep every existing step. The 3.8 leg proves import-time compatibility via the existing python -m py_compile scripts/*.py evals/*.py step plus add one new step before it, so a plain compile pass can't hide import-time TypeErrors:

      - name: Import all scripts (annotation floor check)
        run: |
          for f in scripts/*.py evals/*.py; do python3 -c "import runpy, sys; sys.argv=['x','--help']" >/dev/null 2>&1; python3 - <<EOF
          import importlib.util, pathlib, sys
          p = pathlib.Path("$f")
          spec = importlib.util.spec_from_file_location(p.stem, p)
          m = importlib.util.module_from_spec(spec)
          sys.path.insert(0, str(p.parent))
          try:
              spec.loader.exec_module(m)
          except SystemExit:
              pass
          EOF
          done

NOTE: some scripts execute argparse or read stdin at import only under if __name__ == "__main__" — module import must not block. If any file hangs or errors on import for a non-annotation reason, STOP (condition 3). A simpler alternative acceptable here: a one-line python3 -c "import ast, pathlib; [compile(ast.parse(pathlib.Path(f).read_text()), f, 'exec') for f in ...]" does NOT catch evaluated annotations — the exec_module approach (or actually running the suite on the 3.8 leg) is required. Running the full suite on both legs is the simplest correct choice if runtime permits (~30s per leg).

Verify: python3 -c "import yaml" 2>/dev/null || true; grep -c "3.8" .github/workflows/evals.yml → at least 1

Step 4: Fix the two README count phrasings

Change README.md:267 "439 deterministic cases" → "440 deterministic script cases (439 pass, 1 documented xfail)" and README.md:374 "Source of truth: 439 cases" → "Source of truth: 440 script cases". Leave line 294 untouched.

Verify: python3 scripts/banned_phrase_scan.py README.md → "total_violations": 0 Verify: grep -c "439 deterministic cases" README.md → 0

Test plan

No new eval rows: this is an import-time property no subprocess row can express on a newer interpreter. The CI matrix leg IS the regression test. Locally, the full suite run in Done criteria proves no behavior drift.

Done criteria

  • python3 evals/run_adversarial.py → exit 0, PASS 439 XFAIL 1, FAIL 0
  • All 17 listed files contain from __future__ import annotations
  • .github/workflows/evals.yml has a matrix including "3.8"
  • grep -rn "Python 3.8" README.md still matches (the claim stands, now true)
  • git status shows no files outside the in-scope list
  • plans/README.md status row updated

STOP conditions

  • The "Current state" excerpt at banned_phrase_scan.py:82 doesn't match.
  • The suite fails after adding the future-imports (would mean some code path resolves annotations at runtime — investigate, don't patch around).
  • A module errors or hangs on bare import for a non-annotation reason.
  • You find a get_type_hints/typing.get_args call site on these modules.

Maintenance notes

  • Every NEW script must carry the future-import until the floor is raised; consider noting this in AGENTS.md's conventions when next edited (out of scope here).
  • If the maintainer later decides 3.10+ is the real floor, delete the CI 3.8 leg and the README claim together in one commit — they are a pair.