Skip to content
Merged
11 changes: 8 additions & 3 deletions backend/services/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ def analyze(root):
paths={e["path"].lower() for e in entries}; names={Path(e["path"]).name.lower() for e in entries}
signals={
"readme":any(n.startswith("readme") for n in names),
"tests":any("/test" in p or p.startswith("test") or "/tests/" in p or p.startswith("tests/") for p in paths),
"tests":any(
any(part in {"test", "tests", "__tests__"} for part in p.split("/")[:-1])
or Path(p).name.startswith(("test_", "test."))
or Path(p).name.endswith(("_test.py", "_test.go", ".test.js", ".spec.js", ".test.ts", ".spec.ts", ".test.tsx", ".spec.tsx"))
for p in paths
),
"gitignore":".gitignore" in names,
"manifest":any(n in names for n in ["package.json","requirements.txt","pyproject.toml","pom.xml","cargo.toml","go.mod","composer.json","gemfile"]),
"ci":any(p.startswith(".github/workflows/") for p in paths),
Expand Down Expand Up @@ -70,10 +75,10 @@ def build_context(root,q,max_files=8,max_chars=18000):
tokens={x for x in re.findall(r"[\w-]+", q.casefold()) if len(x)>2}; ranked=[]
for p in iter_files(root):
text=read_text(p); rel=str(p.relative_to(root)).replace("\\","/")
low=(rel+"\n"+text[:30000]).casefold()
low=(rel+"\n"+text[:4000]).casefold()
matched=sum(t in low for t in tokens)
bonus=sum(k in rel.casefold() for k in ["readme","app.","main.","index.","route","api"])
if matched or bonus: ranked.append((matched,bonus,rel,text))
if matched or (not q.strip() and bonus): ranked.append((matched,bonus,rel,text))
ranked.sort(key=lambda x: (-x[0], -x[1], x[2].casefold(), x[2])); out=""; used=0; selected=0
for _,_,rel,text in ranked:
block=f"\n--- FILE: {rel} ---\n{text[:4000]}\n"
Expand Down
2 changes: 1 addition & 1 deletion backend/services/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ def iter_files(root: Path):
try: size = p.stat().st_size
except OSError: continue
if size > 300_000: continue
if p.suffix.lower() in TEXT_EXTENSIONS or p.name.lower() in {"dockerfile","makefile",".gitignore"}:
if p.suffix.lower() in TEXT_EXTENSIONS or p.name.lower().endswith(".env.example") or p.name.lower() in {"dockerfile","makefile",".gitignore"}:
yield p

def read_text(path: Path): return path.read_text(encoding="utf-8", errors="replace")
Expand Down
17 changes: 17 additions & 0 deletions backend/tests/test_analyzer_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,22 @@ def test_context_budget_counts_included_not_considered_files(self):
self.assertNotIn("--- FILE: a_large.py", context)


def test_context_selected_file_exposes_the_query_match(self):
self.write("a_hidden.py", "x" * 5000 + "needle_for_context")
self.write("z_visible.py", "needle_for_context = 42")
context = build_context(self.root, "needle_for_context", max_files=1)
self.assertIn("z_visible.py", context)
self.assertIn("needle_for_context = 42", context)
self.assertNotIn("a_hidden.py", context)


def test_unmatched_question_does_not_return_unrelated_readme(self):
self.write("README.md", "setup notes without the requested identifier")
self.write("app.py", "def start(): pass")
context = build_context(self.root, "nonexistent_unique_identifier")
self.assertIn("No strongly matching files", context)
self.assertNotIn("setup notes", context)


if __name__ == "__main__":
unittest.main()
24 changes: 24 additions & 0 deletions backend/tests/test_repository_safety.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,30 @@ def test_zip_slip_is_refused_without_writing_outside(self):
github.extract_zip(z)
self.assertFalse((self.root / "escape.py").exists())

def test_environment_examples_are_indexed_without_loading_env_secrets(self):
imported = self.repos / ("a" * 12)
imported.mkdir()
(imported / ".env.example").write_text("SAMPLE_OPTION=placeholder")
(imported / "service.env.example").write_text("SERVICE_OPTION=example")
(imported / ".env").write_text("REAL_PRIVATE_SETTING=secret")
listed = {p.name for p in github.iter_files(imported)}
self.assertIn(".env.example", listed)
self.assertIn("service.env.example", listed)
self.assertNotIn(".env", listed)
self.assertIn("SAMPLE_OPTION", build_context(imported, "SAMPLE_OPTION"))
self.assertNotIn("REAL_PRIVATE_SETTING", str(analyze(imported)))


def test_test_signal_ignores_unrelated_source_names(self):
imported = self.repos / ("b" * 12)
imported.mkdir()
(imported / "src").mkdir()
(imported / "src" / "testimonials.py").write_text("def describe(): pass")
(imported / "src" / "contest.py").write_text("def compete(): pass")
self.assertFalse(analyze(imported)["signals"]["tests"])
(imported / "src" / "feature.test.ts").write_text("it('works', () => {})")
self.assertTrue(analyze(imported)["signals"]["tests"])


if __name__ == "__main__":
unittest.main()
Loading