Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **JS/TS test-file excludes suggested by wizard + config help** (GH #165).
Unexcluded co-located test files (`*.spec.ts` / `*.test.ts` / `__tests__`)
were the upstream root cause of graph-export edge pollution (lh-enterprise:
77% of edges from test files, mocks all unresolved). `codeindex init`'s
wizard now suggests the test exclude patterns when such files exist (same
conditional style as `vendor/`/`target/`), and `config explain exclude`
finally lists them — graph-export's high-unresolved warning already pointed
there. Fixed at the config seam (single source of truth for both `scan-all`
and `graph-export`); a separate `--exclude-tests` flag was rejected as it
would let the two see different trees.
- **Characterization tests immune to local fixture pollution** (GH #135
residual). `test_graphbuffer_baseline` copies the fixture including any
on-disk `README_AI.md` left by a repo-root `scan-all` (gitignored by
Expand Down
12 changes: 11 additions & 1 deletion src/codeindex/config_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,20 @@
"type": "list[string]",
"default": "(standard exclusions)",
"description": "Directory patterns to exclude from scanning",
"recommendations": """
• Always: __pycache__, node_modules, .git
• JS/TS projects: exclude co-located test files — they are mock-heavy
and pollute graph-export edges (GH #165):
- "**/*.spec.ts" / "**/*.spec.tsx" / "**/*.spec.js"
- "**/*.test.ts" / "**/*.test.tsx" / "**/*.test.js"
- "**/__tests__/**\"""",
"example": """exclude:
- "**/__pycache__/**"
- "**/node_modules/**"
- "**/.git/**\"""",
- "**/.git/**"
- "**/*.spec.ts"
- "**/*.test.ts"
- "**/__tests__/**\"""",
},
"output_file": {
"name": "output_file",
Expand Down
28 changes: 28 additions & 0 deletions src/codeindex/init_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,18 @@ def infer_include_patterns(project_dir: Path) -> List[str]:
return sorted(includes)


def _has_js_test_files(project_dir: Path) -> bool:
"""True if the project contains JS/TS co-located test files or a __tests__ dir."""
suffixes = (".spec.ts", ".spec.tsx", ".spec.js", ".test.ts", ".test.tsx", ".test.js")
for root, dirs, files in os.walk(project_dir):
dirs[:] = [d for d in dirs if d not in ("node_modules", ".git", "__pycache__")]
if "__tests__" in dirs:
return True
if any(f.endswith(suffixes) for f in files):
return True
return False


def infer_exclude_patterns(project_dir: Path) -> List[str]:
"""Infer exclude patterns based on common artifacts.

Expand Down Expand Up @@ -309,6 +321,22 @@ def infer_exclude_patterns(project_dir: Path) -> List[str]:
if (project_dir / dir_name).exists():
excludes.append(pattern)

# GH #165: JS/TS co-located test files are mock-heavy and near-zero nav
# value; unexcluded they polluted graph-export with 77% test edges.
# Suggested only when present — same conditional style as above.
if _has_js_test_files(project_dir):
excludes.extend(
[
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.spec.js",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.test.js",
"**/__tests__/**",
]
)

return sorted(set(excludes))


Expand Down
53 changes: 53 additions & 0 deletions tests/test_init_wizard_exclude.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""JS/TS test-file exclude suggestions (GH #165).

Root cause of graph-export edge pollution (77% of edges from test files,
mocks all unresolved): the wizard/config template never suggested JS/TS
test patterns, so `.codeindex.yaml` shipped without them. Fixed at the
config seam — a `--exclude-tests` flag would let scan-all and
graph-export see different trees.
"""

from pathlib import Path

from codeindex.config_help import CONFIG_PARAMS
from codeindex.init_wizard import infer_exclude_patterns

JS_TS_TEST_PATTERNS = [
"**/*.spec.ts",
"**/*.spec.tsx",
"**/*.spec.js",
"**/*.test.ts",
"**/*.test.tsx",
"**/*.test.js",
"**/__tests__/**",
]


def test_suggests_test_excludes_when_spec_files_exist(tmp_path: Path) -> None:
(tmp_path / "src").mkdir()
(tmp_path / "src" / "foo.service.ts").write_text("export class Foo {}\n")
(tmp_path / "src" / "foo.service.spec.ts").write_text("describe('Foo', () => {});\n")

excludes = infer_exclude_patterns(tmp_path)

for pattern in JS_TS_TEST_PATTERNS:
assert pattern in excludes, f"missing {pattern}"


def test_no_test_excludes_for_pure_python_project(tmp_path: Path) -> None:
(tmp_path / "src").mkdir()
(tmp_path / "src" / "main.py").write_text("x = 1\n")

excludes = infer_exclude_patterns(tmp_path)

assert not any(p in excludes for p in JS_TS_TEST_PATTERNS)


def test_explain_exclude_lists_test_patterns() -> None:
"""graph-export's warning says 'run: codeindex config explain exclude' —
that output must actually contain the test patterns it points to."""
exclude_help = CONFIG_PARAMS["exclude"]
text = exclude_help.get("recommendations", "") + exclude_help.get("example", "")

assert "**/*.spec.ts" in text
assert "**/__tests__/**" in text
Loading