diff --git a/packages/core/src/repowise/core/generation/selection/scoring.py b/packages/core/src/repowise/core/generation/selection/scoring.py index 91ca58910..f663547d9 100644 --- a/packages/core/src/repowise/core/generation/selection/scoring.py +++ b/packages/core/src/repowise/core/generation/selection/scoring.py @@ -84,9 +84,14 @@ def score_file( base += kg_bonus - # Penalties. + # Penalties. Actual test specs are penalized so substantive production code + # ranks higher under caps; test support/infrastructure files (fixtures, helpers) + # are preserved without penalty. if fi.is_test: - base *= _PENALTY_TEST + from repowise.core.test_paths import is_test_support_path + + if not is_test_support_path(fi.path, getattr(fi, "language", None)): + base *= _PENALTY_TEST if ( n_symbols <= _PENALTY_TRIVIAL_SYMBOL_CAP and fi.size_bytes < _PENALTY_TRIVIAL_SIZE_BYTES diff --git a/packages/core/src/repowise/core/generation/selection/selector.py b/packages/core/src/repowise/core/generation/selection/selector.py index 057c779aa..b3832a716 100644 --- a/packages/core/src/repowise/core/generation/selection/selector.py +++ b/packages/core/src/repowise/core/generation/selection/selector.py @@ -274,27 +274,26 @@ def count_documentable_files(parsed_files: list[Any]) -> int: to do before generation starts, in the same terms the policy uses. """ return sum( - 1 for p in parsed_files if _is_code_file(p) and _passes_importance_floor(p.file_info.path) + 1 + for p in parsed_files + if _is_code_file(p) + and _passes_importance_floor(p.file_info.path, getattr(p.file_info, "language", None)) ) -def _passes_importance_floor(path: str) -> bool: +def _passes_importance_floor(path: str, language: str | None = None) -> bool: """Whether *path* is worth a file page at all. - Two exclusions, both measured rather than assumed: test files and pure - ``__init__.py`` re-export files. Pages for either only dilute retrieval - (test-file pages pushed real answers below rank 5 in dogfood), and neither - says anything a reader cannot get from the file it re-exports or tests. - - This used to gate only the coverage tail, back when the budget picked a - fraction of the repo and the tail backfilled the rest. There is no tail any - more because every file that clears this floor gets a page, so the floor is - now simply what file-page selection means. The rule is unchanged; only the - set it applies to grew from the remainder to the whole. + Two exclusions, both measured rather than assumed: actual test spec files + (is_test_path) and pure ``__init__.py`` re-export files. Test support/fixture + modules (conftest.py, helpers/, fixtures/) pass the floor because they + contain key architectural and harness conventions needed for retrieval. """ - norm = path.replace("\\", "/") - if norm.startswith("tests/") or "/tests/" in norm: + from repowise.core.test_paths import is_test_path + + if is_test_path(path, language): return False + norm = path.replace("\\", "/") return norm.rsplit("/", 1)[-1] != "__init__.py" @@ -317,7 +316,7 @@ def _build_file_candidates( if not _is_code_file(p): continue path = p.file_info.path - if not _passes_importance_floor(path): + if not _passes_importance_floor(path, getattr(p.file_info, "language", None)): continue is_hotspot = bool(git.get(path, {}).get("is_hotspot", False)) s = score_file( diff --git a/packages/core/src/repowise/core/ingestion/languages/specs/javascript.py b/packages/core/src/repowise/core/ingestion/languages/specs/javascript.py index 9a3e119a0..d987babb7 100644 --- a/packages/core/src/repowise/core/ingestion/languages/specs/javascript.py +++ b/packages/core/src/repowise/core/ingestion/languages/specs/javascript.py @@ -8,6 +8,8 @@ display_name="JavaScript", import_support="full", test_infixes=(".test.", ".spec."), + test_fixture_stems=("fixtures", "fixture", "setup-tests"), + test_stem_suffixes=("_test", ".test", ".spec"), extensions=frozenset({".js", ".jsx", ".mjs", ".cjs"}), grammar_package="tree_sitter_javascript", scm_file="javascript.scm", diff --git a/packages/core/src/repowise/core/ingestion/languages/specs/typescript.py b/packages/core/src/repowise/core/ingestion/languages/specs/typescript.py index bb6ff97f2..47e81aba3 100644 --- a/packages/core/src/repowise/core/ingestion/languages/specs/typescript.py +++ b/packages/core/src/repowise/core/ingestion/languages/specs/typescript.py @@ -57,6 +57,8 @@ display_name="TypeScript", import_support="full", test_infixes=(".test.", ".spec."), + test_fixture_stems=("fixtures", "fixture", "setup-tests"), + test_stem_suffixes=("_test", ".test", ".spec"), extensions=frozenset({".ts", ".tsx", ".mts", ".cts"}), grammar_package="tree_sitter_typescript", grammar_loader="language_typescript", diff --git a/tests/unit/generation/test_selection_contract.py b/tests/unit/generation/test_selection_contract.py index 1fe1d99e2..7afb05b60 100644 --- a/tests/unit/generation/test_selection_contract.py +++ b/tests/unit/generation/test_selection_contract.py @@ -156,6 +156,51 @@ def test_importance_floor_excludes_tests_and_reexports(): assert len(sel.file_page_paths) == 6 +def test_test_support_files_pass_importance_floor(): + """Test infrastructure and fixture files pass the floor to receive file pages.""" + parsed, pagerank, betweenness, community = _build_synthetic_repo(6) + extras = [ + ("tests/conftest.py", "python"), + ("tests/fixtures/setup.py", "python"), + ("apps/console/e2e/helpers/hydration.ts", "typescript"), + ("apps/console/e2e/fixtures.ts", "typescript"), + ] + for path, lang in extras: + parsed.append( + FakeParsedFile( + file_info=FakeFileInfo(path=path, language=lang, is_test=True), + symbols=[FakeSymbol(name="fixture_helper")], + ) + ) + pagerank[path] = 0.1 + betweenness[path] = 0.0 + community[path] = 0 + + # Also add pure specs which must be excluded + test_specs = [ + ("tests/unit/test_foo.py", "python"), + ("apps/console/e2e/specs/login.spec.ts", "typescript"), + ] + for path, lang in test_specs: + parsed.append( + FakeParsedFile( + file_info=FakeFileInfo(path=path, language=lang, is_test=True), + symbols=[FakeSymbol(name="test_login")], + ) + ) + pagerank[path] = 0.1 + betweenness[path] = 0.0 + community[path] = 0 + + sel = select_pages(_inputs(parsed, pagerank, betweenness, community, GenerationConfig())) + + for path, _ in extras: + assert path in sel.file_page_paths, f"Expected {path} to receive a file_page" + + for path, _ in test_specs: + assert path not in sel.file_page_paths, f"Expected {path} to be excluded" + + def test_selection_does_not_depend_on_having_a_key(): """Keyed and keyless runs select exactly the same pages. diff --git a/tests/unit/test_test_paths.py b/tests/unit/test_test_paths.py index 74027cec0..9d388e568 100644 --- a/tests/unit/test_test_paths.py +++ b/tests/unit/test_test_paths.py @@ -60,6 +60,9 @@ ("spec/support/helper.rb", None, "support"), # e2e suites ("e2e/login.ts", None, "test"), + ("apps/console/e2e/helpers/hydration.ts", "typescript", "support"), + ("apps/console/e2e/fixtures.ts", "typescript", "support"), + ("apps/console/e2e/specs/login.spec.ts", "typescript", "test"), # production code that merely contains the word: the unanchored # `test[s_/]` substring rule classified the first three as tests ("src/latest/api.py", None, ""), # #1103 finding 1