diff --git a/luminos_lib/ai.py b/luminos_lib/ai.py index bc330a1..4115957 100644 --- a/luminos_lib/ai.py +++ b/luminos_lib/ai.py @@ -756,6 +756,30 @@ def _get_child_summaries(dir_path, cache): _SURVEY_CONFIDENCE_THRESHOLD = 0.5 _PROTECTED_DIR_TOOLS = {"submit_report"} +# Survey-skip thresholds. Skip the survey only when BOTH are below. +# See #46 for the plan to revisit these with empirical data. +_SURVEY_MIN_FILES = 5 +_SURVEY_MIN_DIRS = 2 + + +def _default_survey(): + """Synthetic survey for targets too small to justify the API call. + + confidence=0.0 ensures _filter_dir_tools() never enforces skip_tools + based on this synthetic value — the dir loop keeps its full toolbox. + """ + return { + "description": "Small target — survey skipped.", + "approach": ( + "The target is small enough to investigate exhaustively. " + "Read every file directly." + ), + "relevant_tools": [], + "skip_tools": [], + "domain_notes": "", + "confidence": 0.0, + } + def _format_survey_block(survey): """Render survey output as a labeled text block for the dir prompt.""" @@ -1228,8 +1252,21 @@ def _run_investigation(client, target, report, show_hidden=False, f"{'' if is_new else ' (resumed)'}", file=sys.stderr) print(f" [AI] Cache: {cache.root}/", file=sys.stderr) - print(" [AI] Survey pass...", file=sys.stderr) - survey = _run_survey(client, target, report, tracker, verbose=verbose) + all_dirs = _discover_directories(target, show_hidden=show_hidden, + exclude=exclude) + + total_files = sum((report.get("file_categories") or {}).values()) + total_dirs = len(all_dirs) + if total_files < _SURVEY_MIN_FILES and total_dirs < _SURVEY_MIN_DIRS: + print( + f" [AI] Survey skipped — {total_files} files, {total_dirs} dirs " + f"(below threshold).", + file=sys.stderr, + ) + survey = _default_survey() + else: + print(" [AI] Survey pass...", file=sys.stderr) + survey = _run_survey(client, target, report, tracker, verbose=verbose) if survey: print( f" [AI] Survey: {survey['description']} " @@ -1251,9 +1288,6 @@ def _run_investigation(client, target, report, show_hidden=False, else: print(" [AI] Survey unavailable — proceeding without it.", file=sys.stderr) - all_dirs = _discover_directories(target, show_hidden=show_hidden, - exclude=exclude) - to_investigate = [] cached_count = 0 for d in all_dirs: diff --git a/tests/test_ai_filter.py b/tests/test_ai_filter.py index b396710..466755d 100644 --- a/tests/test_ai_filter.py +++ b/tests/test_ai_filter.py @@ -108,5 +108,29 @@ class FormatSurveyBlockTests(unittest.TestCase): self.assertNotIn("Skip tools", block) +class DefaultSurveyTests(unittest.TestCase): + def test_has_all_required_keys(self): + survey = ai._default_survey() + for key in ("description", "approach", "relevant_tools", + "skip_tools", "domain_notes", "confidence"): + self.assertIn(key, survey) + + def test_confidence_below_filter_threshold(self): + # Must be < _SURVEY_CONFIDENCE_THRESHOLD so _filter_dir_tools() + # never enforces skip_tools from a synthetic survey. + self.assertLess( + ai._default_survey()["confidence"], + ai._SURVEY_CONFIDENCE_THRESHOLD, + ) + + def test_filter_returns_full_toolbox_for_default(self): + all_names = {t["name"] for t in ai._DIR_TOOLS} + result = {t["name"] for t in ai._filter_dir_tools(ai._default_survey())} + self.assertEqual(result, all_names) + + def test_skip_tools_is_empty(self): + self.assertEqual(ai._default_survey()["skip_tools"], []) + + if __name__ == "__main__": unittest.main()