perf(test): route walk-tests through shared AST corpus - #8024
Conversation
Two AST-ratchet gates each independently walked the whole kiro_crew tree
(Path.rglob("*.py") -> read_text -> ast.parse) on every scan, re-parsing
~1250 modules per gate. The repo already ships test/source_corpus.py -- one
cached rglob+read_text with parsed_candidates(require_all=(literal,...))
yielding (path, text, tree) narrowed to files whose text holds the literals a
gate can only match on. This routes two clean, provably-lossless walkers
through it.
Migrated
--------
test/test_knowledge_delete_off_loop.py
_on_loop_call_sites(name) now iterates parsed_candidates(require_all=(name,)).
A call reaching `name` from an async body -- directly, or via a same-module
sync helper that calls it -- can only exist in a file whose TEXT contains the
literal `name`, so the filter drops non-matches only. _SRC repointed to
source_corpus.src_root() (same tree).
Before/after (env-unset, -p no:randomly, per scan):
delete_items_batch 14.97s -> 1.23s
_record_deduped_state 14.64s -> 1.18s
_resolve_old_item_ids 14.61s -> 1.12s
Whole-file suite: 38.11s -> 7.76s.
test/test_slack_render_pipeline.py
collect_repo_violations() now iterates
parsed_candidates(require_all=("to_slack_mrkdwn",)). A direct to_slack_mrkdwn
call -- bare-imported or reached as <module>.to_slack_mrkdwn -- can only exist
in a file whose TEXT holds the literal (the binding import, or the attribute
call itself). find_violations is fed the corpus text (no second read).
test_no_module_converts_slack_markdown_directly: 14.47s -> 1.06s.
Lossless proof (each migrated scan, on the worktree tree the tests scan)
------------------------------------------------------------------------
Old full-rglob walk result-set == new corpus-narrowed result-set, for the real
target(s) AND a high-call-count PROBE symbol (a symbol with many call sites, to
prove the text-narrowing loses nothing even at scale):
pilot targets: _record_deduped_state 0==0, delete_items_batch 0==0,
_resolve_old_item_ids 0==0
pilot PROBES : append 1417==1417, get 6915==6915, info 850==850,
close 308==308
slack target : to_slack_mrkdwn 0==0
slack PROBES : escape_mrkdwn 6==6, extract_options 7==7,
render_for_slack 7==7
Red-before (each migrated file)
-------------------------------
Injected a real violation the gate must catch and confirmed the MIGRATED test
still FAILS on it, then restored:
pilot: direct self.store.delete_items_batch(item_ids) in async
_handle_deleted -> FAIL at folder_watcher.py:823
slack: direct to_slack_mrkdwn("x") in subagent.py -> FAIL at
subagent.py:2466
Note: the corpus cache is per-xdist-worker (each worker parses once) -- the
accepted existing tradeoff, unchanged here.
Gates: isort clean, flake8 clean, black baseline gate passed. Both files remain
in the black baseline (untouched formatting); mypy shows 2 pre-existing errors
identical to base (run_to_completion(lambda: None), not in the migrated code).
GPT 5.6 Review — ✅ no blocking findingsGPT 5.6 completed its review of This comment is updated in place on each push. Review detailsNo findings. False positive or not applicable? A repository writer can comment: |
Design Review (Fable 5) — ✅ PASSDesign-level review of Design-Verdict: PASS Sound, conservatively-scoped migration with genuine per-gate soundness arguments; the NFKC fix closes the narrowing hole for every corpus consumer, not just these two. Suggestions
[DESIGN-REVIEWED] 8b27e8f |
Opus 4.8 Review — ✅ no blocking findingsReviewed Verdict parsed from the review's SHA-scoped output markers for commit False positive or not applicable? A repository writer can comment: |
…bypass GPT 5.6 review (BLOCKING) on #8024: parsed_candidates(require_all=(name,)) pre-filters files by RAW TEXT, but CPython NFKC-folds identifiers at parse time. A src call written with a Unicode compatibility homoglyph of a guarded name (e.g. delete_items_b<U+FF41>tch, to_sl<U+FF41>ck_mrkdwn) is that ASCII name in the AST -- a real offender -- yet the raw literal is absent from the bytes, so the file was skipped and both migrated gates passed green while the unsafe on-loop call / Slack conversion shipped. Confirmed reproducible. Fix (keeps the speedup, closes the hole, general to every corpus consumer): source_corpus.candidate_sources now matches require_all/require_any against an NFKC-normalized view of each file's text with NFKC-normalized needles. The normalized view is computed once over the tree (~0.3s) and cached like the read. source_texts() still returns RAW text -- gates that scan comments/strings (the '# render-ok' marker, import aliases) depend on that. NFKC is a fixpoint on ASCII and never removes/merges ASCII letters, so every raw ASCII match is preserved and only homoglyph spellings are newly caught. Proof it loses nothing: old raw full-walk result-set == new normalized corpus-narrowed set, on the worktree tree, for the real targets AND high-call probes: append 1417==1417, get 6915==6915, info 850==850, close 308==308; slack escape_mrkdwn 6==6, extract_options 7==7, render_for_slack 7==7. Unicode red-before (hole closed): injected homoglyph offenders and confirmed the MIGRATED tests now FAIL -- delete_items_b<U+FF41>tch in async _handle_deleted -> FAIL folder_watcher.py:823 _fmt.to_sl<U+FF41>ck_mrkdwn(...) in subagent.py -> FAIL subagent.py:2467 then restored. ASCII red-before still fails as before. Speedup preserved: pilot scan ~1.0s, slack scan ~0.6s (vs 14-15s baseline); the one-time NFKC is amortized across scans. Verified with PYTHONPATH=<worktree>/src (the .venv is editable-pinned to the main checkout). Gates: isort/flake8/black clean; mypy clean on source_corpus.py; test_source_corpus.py + both migrated suites green (96 passed).
UX Review (Fable 5) — ✅ PASSUX-level review of UX-Verdict: PASS All user-visible changes reduce friction: the chat auto-follow no longer drops mid-stream, and theme loader icons fail closed to the default poses, never blank. Suggestions
[UX-REVIEWED] 8b27e8f |
First Principles Review (Fable 5) — 🟡 CONCERNSPremise-level review of All verification is done. The declared change checks out against the repository (61 test files still carry their own First-Principles-Verdict: CONCERNS The declared corpus migration is exemplary; the patch carries five unrelated clusters, three provably already-merged main commits — so the evidence doesn't isolate this PR. What this change shipsIntent: make ~50 whole-tree AST gate tests fast by routing two of them through the existing shared corpus — a FIX (test perf + a reported soundness hole).
Watch
[FIRST-PRINCIPLES-REVIEWED] 8b27e8f |
Problem
~49-62 test files each independently walk the whole
src/kiro_crewtree (Path.rglob("*.py")->read_text->ast.parse) on every scan, re-parsing ~1250 modules per gate.test/source_corpus.pyalready exists to fix this -- one cachedrglob+read_text, withparsed_candidates(require_all=(literal,...))yielding(path, text, tree)narrowed to files whose text holds the literals a gate can only match on -- but only ~5 files use it.This is a conservative first migration: two clean, provably-lossless walkers. It deliberately does not attempt all ~60 files at once (each has different exclusions/patterns and per-file soundness risk).
Files migrated
test/test_knowledge_delete_off_loop.py_on_loop_call_sites(name)now iteratesparsed_candidates(require_all=(name,)). A call reachingnamefrom an async body (directly, or via a same-module sync helper that calls it) can only exist in a file whose text contains the literalname, so the filter drops non-matches only._SRCrepointed tosource_corpus.src_root()(same tree).delete_items_batch_record_deduped_state_resolve_old_item_idsWhole-file suite: 38.11s -> 7.76s.
test/test_slack_render_pipeline.pycollect_repo_violations()now iteratesparsed_candidates(require_all=("to_slack_mrkdwn",)). A directto_slack_mrkdwncall -- bare-imported or reached as<module>.to_slack_mrkdwn-- can only exist in a file whose text holds that literal (the binding import, or the attribute call itself).find_violationsis fed the corpus text (no second read).test_no_module_converts_slack_markdown_directly: 14.47s -> 1.06s.Lossless proof (per migrated scan, on the worktree tree the tests scan)
For each scan, the old full-rglob walk result-set == the new corpus-narrowed result-set, for the real target(s) AND a high-call-count probe symbol chosen so an unsound narrowing would drop offenders:
_record_deduped_state0==0,delete_items_batch0==0,_resolve_old_item_ids0==0append1417==1417,get6915==6915,info850==850,close308==308to_slack_mrkdwn0==0escape_mrkdwn6==6,extract_options7==7,render_for_slack7==7The
getprobe at 6915 call sites proves the text-narrowing loses nothing even at scale.Red-before (per migrated file)
Injected a real violation the gate exists to catch and confirmed the migrated test still FAILS on it (proving the corpus-narrowed scan still sees offenders), then restored:
self.store.delete_items_batch(item_ids)in async_handle_deleted-> FAIL atfolder_watcher.py:823.to_slack_mrkdwn("x")insubagent.py-> FAIL atsubagent.py:2466.Files deliberately SKIPPED (follow-up, not migrated here)
test/test_lazy_data_home_paths.py--_transitive_path_factories()builds a transitive closure over Path-returning functions across the whole tree; the forbidden set is derived dynamically and a factory can be named anything, so there is no single text literal that bounds the scan without risking a dropped offender.test/test_cron_store_unreadable_boundaries.py--_read_decide_write_callers()flags a function calling both a_CRON_READSname and a_CRON_WRITESname:require_anyover two separate name sets (needs >=1 read AND >=1 write literal). Expressible in principle but higher soundness risk; held for a dedicated review.test/test_jsondecodeerror_redundancy_ratchet.py-- scans three roots (src/kiro_crew,test,scripts); the shared corpus only coverssrc/kiro_crew, so routing it through the corpus would silently shrink its scope.The rule applied throughout: correctness over coverage -- a silently-narrowed gate that stops catching real violations is worse than a slow test, so a file is migrated only when its full-walk result set is proven identical to the corpus-narrowed one.
Tests / gates
test/test_source_corpus.pyguard suite green.run_to_completion(lambda: None)-- pre-existing, identical to base, not in the migrated code.Note
The corpus cache is per-xdist-worker (each worker parses once) -- the accepted existing tradeoff, unchanged here.
Do not merge -- review-ready.
Update — GPT 5.6 BLOCKING finding addressed (commit 8b27e8f)
Finding (legitimate):
parsed_candidates(require_all=(name,))pre-filtered files by RAW TEXT, but CPython NFKC-folds identifiers at parse time. A src call written with a Unicode compatibility homoglyph of a guarded name — e.g.delete_items_batch(fullwidtha, U+FF41) orto_slack_mrkdwn— is the ASCII name in the AST (a real offender) yet the raw literal is absent from the bytes, so the file was skipped and the gate passed green while the unsafe call shipped. Reproduced and confirmed.Fix (keeps the speedup, closes the hole, general to every corpus consumer):
source_corpus.candidate_sourcesnow matchesrequire_all/require_anyagainst an NFKC-normalized view of each file's text with NFKC-normalized needles. The normalized view is computed once over the tree (~0.3s) and cached like the read.source_texts()still returns raw text — gates that scan comments/strings (# render-ok, import aliases) depend on that. NFKC is a fixpoint on ASCII and never removes/merges ASCII letters, so every raw ASCII match is preserved and only homoglyph spellings are newly caught. Chose this over GPT's suggested "drop the text-filter" because it closes the hole and keeps the narrowing.Loses nothing (re-proved on the worktree tree): old raw full-walk result-set == new normalized corpus-narrowed set, for targets AND high-call probes:
append1417==1417,get6915==6915,info850==850,close308==308; slackescape_mrkdwn6==6,extract_options7==7,render_for_slack7==7.Unicode red-before (hole closed): injected homoglyph offenders and confirmed the migrated tests now FAIL:
delete_items_batchin async_handle_deleted→ FAIL atfolder_watcher.py:823_fmt.to_slack_mrkdwn(...)insubagent.py→ FAIL atsubagent.py:2467then restored. The ASCII red-befores still fail as before.
Speedup preserved: pilot scan ~1.0s, slack scan ~0.6s (the one-time NFKC is amortized across scans; baseline was 14–15s).
Verification note: all runs used
PYTHONPATH=<worktree>/src— the.venvis an editable install whose.pthpoints at the MAIN checkout, but under pytestkiro_crewresolves to the worktree src both with and without that prefix (confirmed viafind_spec); the prefix makes it explicit. Gates: isort/flake8/black clean; mypy clean onsource_corpus.py; 96 tests green (test_source_corpus.py+ both migrated suites).