# Subsystem: tests (page 1 of 4)
Pages: [KB_tests.md](KB_tests.md), [KB_tests_p2.md](KB_tests_p2.md), [KB_tests_p3.md](KB_tests_p3.md), [KB_tests_p4.md](KB_tests_p4.md)

## tests/__init__.py
- Layer: testing
- Language: py

## tests/test_agent_friendliness.py
- Doc: Contract tests for agent-facing output quality: budgets, purposes, freshness, noise.
- Layer: testing
- Language: py
- Symbols:
  - `_node` (function, line 21) `def _node(node_id, doc, symbols)`
  - `_edge` (function, line 29) `def _edge(source, target, relation)`
  - `_big_project` (function, line 34) `def _big_project(files, symbols_per_file)`
  - `TestAgentOutputBudget` (class, line 55) `class TestAgentOutputBudget(TestCase)`
  - `TestAgentOutputSignal` (class, line 110) `class TestAgentOutputSignal(TestCase)`
  - `TestPurposeExtraction` (class, line 176) `class TestPurposeExtraction(TestCase)`
  - `TestManifestFreshness` (class, line 199) `class TestManifestFreshness(TestCase)`
  - `TestNoiseReduction` (class, line 244) `class TestNoiseReduction(TestCase)`
  - `TestLlmsTxt` (class, line 281) `class TestLlmsTxt(TestCase)`
  - `TestCommunityHubDamping` (class, line 308) `class TestCommunityHubDamping(TestCase)`
  - `TestSourceFreshness` (class, line 329) `class TestSourceFreshness(TestCase)`
  - `TestCommunityShaping` (class, line 363) `class TestCommunityShaping(TestCase)`
  - `TestSiteDocsPruning` (class, line 394) `class TestSiteDocsPruning(TestCase)`
  - `TestLouvainCommunities` (class, line 411) `class TestLouvainCommunities(TestCase)`
  - `TestGalleryIndex` (class, line 456) `class TestGalleryIndex(TestCase)`
  - `TestLiveMapCommunities` (class, line 512) `class TestLiveMapCommunities(TestCase)`
  - `test_agent_output_pages_respect_line_cap_on_large_projects` (method, line 58) `def test_agent_output_pages_respect_line_cap_on_large_projects(self)`
  - `test_agent_output_pagination_keeps_every_symbol_greppable` (method, line 71) `def test_agent_output_pagination_keeps_every_symbol_greppable(self)`
  - `test_agent_output_pages_repeat_table_header_and_link_next` (method, line 84) `def test_agent_output_pages_repeat_table_header_and_link_next(self)`
  - `test_agent_output_prunes_stale_pages` (method, line 96) `def test_agent_output_prunes_stale_pages(self)`
  - `test_api_states_dependencies_once_per_file` (method, line 113) `def test_api_states_dependencies_once_per_file(self)`
  - `test_api_skips_private_helpers_and_test_layer` (method, line 122) `def test_api_skips_private_helpers_and_test_layer(self)`
  - `test_api_qualifies_methods_with_owner_class` (method, line 136) `def test_api_qualifies_methods_with_owner_class(self)`
  - `test_architecture_external_excludes_internally_resolved_imports` (method, line 144) `def test_architecture_external_excludes_internally_resolved_imports(self)`
  - `test_index_reports_used_by_count_and_escapes_pipes` (method, line 153) `def test_index_reports_used_by_count_and_escapes_pipes(self)`
  - `test_gotchas_exclude_test_layer_and_report_blast_radius` (method, line 161) `def test_gotchas_exclude_test_layer_and_report_blast_radius(self)`
  - `test_purpose_takes_first_sentence` (method, line 179) `def test_purpose_takes_first_sentence(self)`
  - `test_purpose_skips_banners_and_spdx` (method, line 182) `def test_purpose_skips_banners_and_spdx(self)`
  - `test_purpose_falls_back_to_primary_public_symbol` (method, line 185) `def test_purpose_falls_back_to_primary_public_symbol(self)`
  - `test_purpose_truncates_on_word_boundary` (method, line 192) `def test_purpose_truncates_on_word_boundary(self)`
  - `_git_repo` (method, line 202) `def _git_repo(self, root, packed)`
  - `test_gitmeta_reads_loose_and_packed_refs` (method, line 214) `def test_gitmeta_reads_loose_and_packed_refs(self)`
  - `test_gitmeta_outside_repository_is_empty` (method, line 220) `def test_gitmeta_outside_repository_is_empty(self)`
  - `test_manifest_has_commit_relative_root_and_inventory` (method, line 224) `def test_manifest_has_commit_relative_root_and_inventory(self)`
  - `test_scanner_skips_own_generated_outputs` (method, line 247) `def test_scanner_skips_own_generated_outputs(self)`
  - `test_resolver_prefers_root_package_over_launcher_shim` (method, line 260) `def test_resolver_prefers_root_package_over_launcher_shim(self)`
  - `test_layers_match_whole_words_not_substrings` (method, line 264) `def test_layers_match_whole_words_not_substrings(self)`
  - `test_layers_test_framework_import_needs_test_path` (method, line 272) `def test_layers_test_framework_import_needs_test_path(self)`
  - `test_llms_txt_lists_wiki_before_agent_docs` (method, line 284) `def test_llms_txt_lists_wiki_before_agent_docs(self)`
  - `test_publish_writes_llms_txt` (method, line 296) `def test_publish_writes_llms_txt(self)`
  - `test_communities_survive_a_shared_hub` (method, line 311) `def test_communities_survive_a_shared_hub(self)`
  - `test_fingerprint_changes_with_content_not_with_order` (method, line 332) `def test_fingerprint_changes_with_content_not_with_order(self)`
  - `test_check_freshness_detects_source_edits` (method, line 343) `def test_check_freshness_detects_source_edits(self)`
  - `test_small_community_merges_into_best_connected_neighbor` (method, line 366) `def test_small_community_merges_into_best_connected_neighbor(self)`
  - `test_shared_directory_labels_use_core_file` (method, line 380) `def test_shared_directory_labels_use_core_file(self)`
  - `test_publish_assets_prunes_stale_markdown` (method, line 397) `def test_publish_assets_prunes_stale_markdown(self)`
  - `_two_cliques` (method, line 414) `def _two_cliques(self)`
  - `test_louvain_splits_bridged_cliques` (method, line 426) `def test_louvain_splits_bridged_cliques(self)`
  - `test_louvain_is_deterministic_and_numbered_by_size` (method, line 435) `def test_louvain_is_deterministic_and_numbered_by_size(self)`
  - `test_community_label_ignores_test_directory` (method, line 446) `def test_community_label_ignores_test_directory(self)`
  - `_entries` (method, line 459) `def _entries(self)`
  - `test_gallery_groups_docs_and_collapses_pages` (method, line 470) `def test_gallery_groups_docs_and_collapses_pages(self)`
  - `test_gallery_video_has_poster_and_start_here` (method, line 480) `def test_gallery_video_has_poster_and_start_here(self)`
  - `test_gallery_has_no_external_resources_and_escapes_titles` (method, line 486) `def test_gallery_has_no_external_resources_and_escapes_titles(self)`
  - `test_gallery_footer_links_github_and_pypi` (method, line 497) `def test_gallery_footer_links_github_and_pypi(self)`
  - `test_doc_preview_skips_markdown_syntax` (method, line 505) `def test_doc_preview_skips_markdown_syntax(self)`
  - `_inputs` (method, line 515) `def _inputs(self)`
  - `test_built_maps_carry_community_and_core_role` (method, line 531) `def test_built_maps_carry_community_and_core_role(self)`
  - `test_vis_render_includes_legend_and_dot_scaling` (method, line 540) `def test_vis_render_includes_legend_and_dot_scaling(self)`
- Depends on: `readmenator/_agent_output.py`, `readmenator/_analyzer.py`, `readmenator/_app.py`, `readmenator/_cache.py`, `readmenator/_config.py`, `readmenator/_diagrams.py`, `readmenator/_gitmeta.py`, `readmenator/_layers.py`, `readmenator/_models.py`, `readmenator/_purpose.py`, `readmenator/_resolver.py`, `readmenator/_scanner.py`

## tests/test_agent_injector.py
- Doc: Contract tests for AI agent file injection.
- Layer: testing
- Language: py
- Symbols:
  - `TestAgentInjectorInjectBehavior` (class, line 19) `class TestAgentInjectorInjectBehavior(TestCase)`
  - `TestAgentInjectorRemoveBehavior` (class, line 161) `class TestAgentInjectorRemoveBehavior(TestCase)`
  - `TestAgentInjectorFindFiles` (class, line 204) `class TestAgentInjectorFindFiles(TestCase)`
  - `TestAgentInjectorEdgeCases` (class, line 241) `class TestAgentInjectorEdgeCases(TestCase)`
  - `setUp` (method, line 22) `def setUp(self)`
  - `tearDown` (method, line 27) `def tearDown(self)`
  - `test_inject_into_agents_md_adds_kb_link` (method, line 30) `def test_inject_into_agents_md_adds_kb_link(self)`
  - `test_inject_into_claude_md_adds_kb_link` (method, line 39) `def test_inject_into_claude_md_adds_kb_link(self)`
  - `test_inject_into_cursorrules_adds_kb_link` (method, line 49) `def test_inject_into_cursorrules_adds_kb_link(self)`
  - `test_inject_into_github_copilot_instructions` (method, line 57) `def test_inject_into_github_copilot_instructions(self)`
  - `test_inject_replaces_old_injection_without_regen_command` (method, line 67) `def test_inject_replaces_old_injection_without_regen_command(self)`
  - `test_inject_skips_when_already_up_to_date` (method, line 82) `def test_inject_skips_when_already_up_to_date(self)`
  - `test_inject_into_cursor_rules_mdc_glob` (method, line 89) `def test_inject_into_cursor_rules_mdc_glob(self)`
  - `test_inject_is_idempotent_does_not_duplicate` (method, line 99) `def test_inject_is_idempotent_does_not_duplicate(self)`
  - `test_inject_no_agent_files_returns_zero` (method, line 110) `def test_inject_no_agent_files_returns_zero(self)`
  - `test_inject_preserves_existing_content` (method, line 114) `def test_inject_preserves_existing_content(self)`
  - `test_inject_multiple_agent_files` (method, line 122) `def test_inject_multiple_agent_files(self)`
  - `test_inject_plain_text_format_for_yaml` (method, line 129) `def test_inject_plain_text_format_for_yaml(self)`
  - `test_custom_kb_filename_works` (method, line 138) `def test_custom_kb_filename_works(self)`
  - `test_injection_includes_regeneration_command` (method, line 146) `def test_injection_includes_regeneration_command(self)`
  - `test_inject_does_not_execute_commands` (method, line 153) `def test_inject_does_not_execute_commands(self)`
  - `setUp` (method, line 164) `def setUp(self)`
  - `tearDown` (method, line 169) `def tearDown(self)`
  - `test_remove_strips_injected_section` (method, line 172) `def test_remove_strips_injected_section(self)`
  - `test_remove_without_injection_returns_zero` (method, line 183) `def test_remove_without_injection_returns_zero(self)`
  - `test_remove_no_files_returns_zero` (method, line 189) `def test_remove_no_files_returns_zero(self)`
  - `test_remove_preserves_original_content` (method, line 193) `def test_remove_preserves_original_content(self)`
  - `setUp` (method, line 207) `def setUp(self)`
  - `tearDown` (method, line 211) `def tearDown(self)`
  - `test_finds_agents_md` (method, line 214) `def test_finds_agents_md(self)`
  - `test_finds_all_listed_files` (method, line 220) `def test_finds_all_listed_files(self)`
  - `test_finds_cursor_rules_glob` (method, line 227) `def test_finds_cursor_rules_glob(self)`
  - `test_returns_empty_when_no_files` (method, line 236) `def test_returns_empty_when_no_files(self)`
  - `setUp` (method, line 244) `def setUp(self)`
  - `tearDown` (method, line 249) `def tearDown(self)`
  - `test_inject_into_empty_file` (method, line 252) `def test_inject_into_empty_file(self)`
  - `test_inject_respects_custom_agent_files_list` (method, line 260) `def test_inject_respects_custom_agent_files_list(self)`
  - `test_inject_does_not_touch_unlisted_files` (method, line 268) `def test_inject_does_not_touch_unlisted_files(self)`
- Depends on: `readmenator/_agent_injector.py`

## tests/test_agent_output.py
- Layer: testing
- Language: py
- Symbols:
  - `_make_node` (function, line 19) `def _make_node(node_id, symbols, doc, language)`
  - `_make_edge` (function, line 30) `def _make_edge(source, target, relation)`
  - `_make_finding` (function, line 34) `def _make_finding(file_path, line, severity, rule_id, description, snippet, cwe)`
  - `TestAgentOutputContract` (class, line 49) `class TestAgentOutputContract(TestCase)`
  - `TestSubsystemInference` (class, line 63) `class TestSubsystemInference(TestCase)`
  - `TestIndexGeneration` (class, line 117) `class TestIndexGeneration(TestCase)`
  - `TestSecurityGeneration` (class, line 143) `class TestSecurityGeneration(TestCase)`
  - `TestGotchasGeneration` (class, line 190) `class TestGotchasGeneration(TestCase)`
  - `TestArchitectureGeneration` (class, line 246) `class TestArchitectureGeneration(TestCase)`
  - `TestApiGeneration` (class, line 268) `class TestApiGeneration(TestCase)`
  - `TestSubsystemFileGeneration` (class, line 294) `class TestSubsystemFileGeneration(TestCase)`
  - `TestRecipesGeneration` (class, line 316) `class TestRecipesGeneration(TestCase)`
  - `TestFullGenerate` (class, line 362) `class TestFullGenerate(TestCase)`
  - `TestInjectionOutdatedDetection` (class, line 434) `class TestInjectionOutdatedDetection(TestCase)`
  - `test_config_defaults` (method, line 50) `def test_config_defaults(self)`
  - `test_config_immutable` (method, line 56) `def test_config_immutable(self)`
  - `test_inferred_from_directories` (method, line 64) `def test_inferred_from_directories(self)`
  - `test_flat_project_single_file` (method, line 80) `def test_flat_project_single_file(self)`
  - `test_min_threshold_respected` (method, line 91) `def test_min_threshold_respected(self)`
  - `test_misc_catches_unassigned` (method, line 103) `def test_misc_catches_unassigned(self)`
  - `test_index_lists_all_files` (method, line 118) `def test_index_lists_all_files(self)`
  - `test_index_table_format` (method, line 133) `def test_index_table_format(self)`
  - `test_empty_findings` (method, line 144) `def test_empty_findings(self)`
  - `test_findings_grouped_by_severity` (method, line 150) `def test_findings_grouped_by_severity(self)`
  - `test_findings_include_fix_hint_and_scope` (method, line 166) `def test_findings_include_fix_hint_and_scope(self)`
  - `test_no_json_wrapping` (method, line 181) `def test_no_json_wrapping(self)`
  - `test_god_nodes_section` (method, line 191) `def test_god_nodes_section(self)`
  - `test_cycles_section` (method, line 207) `def test_cycles_section(self)`
  - `test_empty_gotchas` (method, line 224) `def test_empty_gotchas(self)`
  - `test_cycle_loop_closed` (method, line 230) `def test_cycle_loop_closed(self)`
  - `test_internal_dependencies` (method, line 247) `def test_internal_dependencies(self)`
  - `test_external_imports` (method, line 258) `def test_external_imports(self)`
  - `test_functions_listed` (method, line 269) `def test_functions_listed(self)`
  - `test_no_json_in_api` (method, line 283) `def test_no_json_in_api(self)`
  - `test_subsystem_files_written` (method, line 295) `def test_subsystem_files_written(self)`
  - `test_recipes_directory` (method, line 317) `def test_recipes_directory(self)`
  - `test_recipes_grounded_in_actual_findings` (method, line 330) `def test_recipes_grounded_in_actual_findings(self)`
  - `test_generate_creates_all_files` (method, line 363) `def test_generate_creates_all_files(self)`
  - `test_all_files_under_500_lines` (method, line 394) `def test_all_files_under_500_lines(self)`
  - `test_no_json_in_any_output` (method, line 409) `def test_no_json_in_any_output(self)`
  - `test_manifest_workflow_orients_with_ls` (method, line 421) `def test_manifest_workflow_orients_with_ls(self)`
  - `test_agent_injector_detects_outdated` (method, line 435) `def test_agent_injector_detects_outdated(self)`
  - `test_agent_injector_skips_identical` (method, line 457) `def test_agent_injector_skips_identical(self)`
  - `test_readme_injector_detects_outdated` (method, line 473) `def test_readme_injector_detects_outdated(self)`
  - `test_readme_injector_skips_identical` (method, line 495) `def test_readme_injector_skips_identical(self)`
- Depends on: `readmenator/_agent_injector.py`, `readmenator/_agent_output.py`, `readmenator/_config.py`, `readmenator/_models.py`, `readmenator/_readme_injector.py`

## tests/test_analyzer.py
- Doc: Contract tests for the GraphAnalyzer.
- Layer: testing
- Language: py
- Symbols:
  - `TestGraphAnalyzerContract` (class, line 16) `class TestGraphAnalyzerContract(TestCase)`
  - `setUp` (method, line 19) `def setUp(self)`
  - `_make_node` (method, line 23) `def _make_node(self, nid, label, lang)`
  - `_make_edge` (method, line 26) `def _make_edge(self, src, tgt, rel)`
  - `test_analyze_empty_graph_returns_empty_result` (method, line 29) `def test_analyze_empty_graph_returns_empty_result(self)`
  - `test_analyze_detects_communities_for_connected_graph` (method, line 34) `def test_analyze_detects_communities_for_connected_graph(self)`
  - `test_analyze_computes_god_nodes` (method, line 48) `def test_analyze_computes_god_nodes(self)`
  - `test_analyze_finds_surprising_connections` (method, line 64) `def test_analyze_finds_surprising_connections(self)`
  - `test_analyze_generates_questions` (method, line 81) `def test_analyze_generates_questions(self)`
  - `test_community_cohesion_is_between_zero_and_one` (method, line 92) `def test_community_cohesion_is_between_zero_and_one(self)`
  - `test_isolated_nodes_do_not_form_communities` (method, line 107) `def test_isolated_nodes_do_not_form_communities(self)`
  - `test_analyze_with_resolved_edges_counts_them` (method, line 116) `def test_analyze_with_resolved_edges_counts_them(self)`
  - `test_analyze_is_repeatable` (method, line 130) `def test_analyze_is_repeatable(self)`
  - `test_dominant_directory_prefers_specific_on_tie` (method, line 141) `def test_dominant_directory_prefers_specific_on_tie(self)`
- Depends on: `readmenator/_analyzer.py`, `readmenator/_config.py`, `readmenator/_models.py`

## tests/test_apidocs.py
- Doc: Contract tests for the docstring parser and the deterministic API reference.
- Layer: testing
- Language: py
- Symbols:
  - `_project` (function, line 144) `def _project()`
  - `TestDocstringParser` (class, line 163) `class TestDocstringParser(TestCase)`
  - `TestApiReferenceBuilder` (class, line 257) `class TestApiReferenceBuilder(TestCase)`
  - `TestSignatureParsing` (class, line 380) `class TestSignatureParsing(TestCase)`
  - `TestApiRendering` (class, line 403) `class TestApiRendering(TestCase)`
  - `TestApiDocsIntegration` (class, line 541) `class TestApiDocsIntegration(TestCase)`
  - `test_docstrings_google_sections` (method, line 166) `def test_docstrings_google_sections(self)`
  - `test_docstrings_numpy_sections` (method, line 181) `def test_docstrings_numpy_sections(self)`
  - `test_docstrings_rest_fields_and_directives` (method, line 191) `def test_docstrings_rest_fields_and_directives(self)`
  - `test_docstrings_jsdoc_and_doxygen_tags` (method, line 203) `def test_docstrings_jsdoc_and_doxygen_tags(self)`
  - `test_docstrings_phpdoc_type_before_variable` (method, line 218) `def test_docstrings_phpdoc_type_before_variable(self)`
  - `test_docstrings_csharp_xml` (method, line 222) `def test_docstrings_csharp_xml(self)`
  - `test_docstrings_rustdoc_sections` (method, line 231) `def test_docstrings_rustdoc_sections(self)`
  - `test_docstrings_plain_and_empty` (method, line 237) `def test_docstrings_plain_and_empty(self)`
  - `test_docstrings_recover_block_and_line_comments` (method, line 244) `def test_docstrings_recover_block_and_line_comments(self)`
  - `test_docstrings_recover_elixir_doc_heredoc` (method, line 252) `def test_docstrings_recover_elixir_doc_heredoc(self)`
  - `setUpClass` (method, line 261) `def setUpClass(cls)`
  - `test_apidocs_python_signatures_exact` (method, line 265) `def test_apidocs_python_signatures_exact(self)`
  - `test_apidocs_python_nesting_and_kinds` (method, line 277) `def test_apidocs_python_nesting_and_kinds(self)`
  - `test_apidocs_python_all_and_private_filter` (method, line 285) `def test_apidocs_python_all_and_private_filter(self)`
  - `test_apidocs_tests_excluded_by_default` (method, line 294) `def test_apidocs_tests_excluded_by_default(self)`
  - `test_apidocs_constructor_inherits_class_args` (method, line 301) `def test_apidocs_constructor_inherits_class_args(self)`
  - `test_apidocs_inheritance_links_and_doc_inheritance` (method, line 305) `def test_apidocs_inheritance_links_and_doc_inheritance(self)`
  - `test_apidocs_c_prototype_doc_merges_into_definition` (method, line 314) `def test_apidocs_c_prototype_doc_merges_into_definition(self)`
  - `test_apidocs_call_graph_resolved` (method, line 322) `def test_apidocs_call_graph_resolved(self)`
  - `test_apidocs_receiver_filter_drops_unproven_method_calls` (method, line 328) `def test_apidocs_receiver_filter_drops_unproven_method_calls(self)`
  - `test_apidocs_annotated_receiver_resolves_method` (method, line 332) `def test_apidocs_annotated_receiver_resolves_method(self)`
  - `test_apidocs_warnings_cover_every_rule` (method, line 336) `def test_apidocs_warnings_cover_every_rule(self)`
  - `test_apidocs_coverage_counts` (method, line 346) `def test_apidocs_coverage_counts(self)`
  - `test_apidocs_deterministic` (method, line 352) `def test_apidocs_deterministic(self)`
  - `test_apidocs_privacy_mode_strips_text` (method, line 360) `def test_apidocs_privacy_mode_strips_text(self)`
  - `test_apidocs_entity_budget_truncates` (method, line 368) `def test_apidocs_entity_budget_truncates(self)`
  - `test_apidocs_without_content_uses_parser_symbols` (method, line 374) `def test_apidocs_without_content_uses_parser_symbols(self)`
  - `test_apidocs_signature_languages` (method, line 383) `def test_apidocs_signature_languages(self)`
  - `setUpClass` (method, line 407) `def setUpClass(cls)`
  - `test_apidocs_pages_layout` (method, line 413) `def test_apidocs_pages_layout(self)`
  - `test_apidocs_module_page_sections` (method, line 418) `def test_apidocs_module_page_sections(self)`
  - `test_apidocs_hierarchy_nests_subclasses` (method, line 429) `def test_apidocs_hierarchy_nests_subclasses(self)`
  - `test_apidocs_linker_resolves_references` (method, line 433) `def test_apidocs_linker_resolves_references(self)`
  - `test_apidocs_html_is_self_contained_and_escaped` (method, line 442) `def test_apidocs_html_is_self_contained_and_escaped(self)`
  - `test_apidocs_html_placeholder_in_payload_stays_literal` (method, line 450) `def test_apidocs_html_placeholder_in_payload_stays_literal(self)`
  - `test_apidocs_html_large_payload_is_gzip_base64` (method, line 454) `def test_apidocs_html_large_payload_is_gzip_base64(self)`
  - `test_apidocs_html_compression_disabled_keeps_plain_json` (method, line 464) `def test_apidocs_html_compression_disabled_keeps_plain_json(self)`
  - `test_apidocs_writer_writes_and_prunes` (method, line 470) `def test_apidocs_writer_writes_and_prunes(self)`
  - `test_apidocs_writer_jsonl_one_file_per_module_one_line_per_entity` (method, line 485) `def test_apidocs_writer_jsonl_one_file_per_module_one_line_per_entity(self)`
  - `test_apidocs_writer_prunes_stale_jsonl_and_monolith` (method, line 512) `def test_apidocs_writer_prunes_stale_jsonl_and_monolith(self)`
  - `test_apidocs_writer_refuses_symlinked_output` (method, line 529) `def test_apidocs_writer_refuses_symlinked_output(self)`
  - `test_apidocs_app_export_and_scanner_skip` (method, line 544) `def test_apidocs_app_export_and_scanner_skip(self)`
- Depends on: `readmenator/_apidocs.py`, `readmenator/_apidocs_page.py`, `readmenator/_apidocs_render.py`, `readmenator/_app.py`, `readmenator/_config.py`, `readmenator/_docstrings.py`, `readmenator/_models.py`

## tests/test_bundlegraph.py
- Doc: Contract tests for the edge bundle explorer (circle and sphere views).
- Layer: testing
- Language: py
- Symbols:
  - `_node` (function, line 19) `def _node(nid, doc)`
  - `_fixture` (function, line 24) `def _fixture(config)`
  - `_script_json` (function, line 49) `def _script_json(html, name)`
  - `TestBundleGraphPayloadContract` (class, line 58) `class TestBundleGraphPayloadContract(TestCase)`
  - `TestBundleGraphRenderContract` (class, line 108) `class TestBundleGraphRenderContract(TestCase)`
  - `TestBundleGraphAppContract` (class, line 172) `class TestBundleGraphAppContract(TestCase)`
  - `setUp` (method, line 61) `def setUp(self)`
  - `test_bundlegraph_groups_follow_community_order_with_unassigned_last` (method, line 67) `def test_bundlegraph_groups_follow_community_order_with_unassigned_last(self)`
  - `test_bundlegraph_edges_are_distinct_file_pairs` (method, line 73) `def test_bundlegraph_edges_are_distinct_file_pairs(self)`
  - `test_bundlegraph_nodes_on_circle_and_sphere` (method, line 81) `def test_bundlegraph_nodes_on_circle_and_sphere(self)`
  - `test_bundlegraph_hub_leads_its_arc` (method, line 87) `def test_bundlegraph_hub_leads_its_arc(self)`
  - `test_bundlegraph_empty_payload` (method, line 95) `def test_bundlegraph_empty_payload(self)`
  - `test_bundlegraph_privacy_mode_strips_docs` (method, line 101) `def test_bundlegraph_privacy_mode_strips_docs(self)`
  - `setUp` (method, line 111) `def setUp(self)`
  - `test_bundlegraph_render_fills_placeholders` (method, line 118) `def test_bundlegraph_render_fills_placeholders(self)`
  - `test_bundlegraph_render_has_no_network_requests` (method, line 127) `def test_bundlegraph_render_has_no_network_requests(self)`
  - `test_bundlegraph_render_has_both_views_and_controls` (method, line 134) `def test_bundlegraph_render_has_both_views_and_controls(self)`
  - `test_bundlegraph_render_escapes_untrusted_text` (method, line 140) `def test_bundlegraph_render_escapes_untrusted_text(self)`
  - `test_bundlegraph_render_payload_naming_a_token_stays_literal` (method, line 151) `def test_bundlegraph_render_payload_naming_a_token_stays_literal(self)`
  - `test_bundlegraph_thumbnail_is_safe_svg` (method, line 157) `def test_bundlegraph_thumbnail_is_safe_svg(self)`
  - `test_bundlegraph_write_creates_parent` (method, line 164) `def test_bundlegraph_write_creates_parent(self)`
  - `_project` (method, line 175) `def _project(self, tmp)`
  - `_config` (method, line 180) `def _config(self)`
  - `test_export_diagrams_writes_bundle_page_and_card` (method, line 185) `def test_export_diagrams_writes_bundle_page_and_card(self)`
  - `test_bundle_page_skipped_when_disabled` (method, line 201) `def test_bundle_page_skipped_when_disabled(self)`
  - `test_export_bundlegraph_cli_entry` (method, line 212) `def test_export_bundlegraph_cli_entry(self)`
  - `test_pages_subdir_card_points_into_maps_dir` (method, line 222) `def test_pages_subdir_card_points_into_maps_dir(self)`
- Depends on: `readmenator/_app.py`, `readmenator/_bundlegraph.py`, `readmenator/_config.py`, `readmenator/_forcegraph.py`, `readmenator/_htmlpack.py`, `readmenator/_models.py`

## tests/test_cache.py
- Doc: Contract tests for the FileCache.
- Layer: testing
- Language: py
- Symbols:
  - `TestFileCacheContract` (class, line 18) `class TestFileCacheContract(TestCase)`
  - `setUp` (method, line 21) `def setUp(self)`
  - `tearDown` (method, line 26) `def tearDown(self)`
  - `_write` (method, line 30) `def _write(self, rel_path, content)`
  - `test_compute_hash_returns_hex_string` (method, line 36) `def test_compute_hash_returns_hex_string(self)`
  - `test_different_content_produces_different_hash` (method, line 42) `def test_different_content_produces_different_hash(self)`
  - `test_same_content_produces_same_hash` (method, line 49) `def test_same_content_produces_same_hash(self)`
  - `test_load_returns_empty_dict_when_no_cache` (method, line 56) `def test_load_returns_empty_dict_when_no_cache(self)`
  - `test_save_and_load_roundtrip` (method, line 60) `def test_save_and_load_roundtrip(self)`
  - `test_find_changed_detects_new_files` (method, line 66) `def test_find_changed_detects_new_files(self)`
  - `test_find_changed_detects_modified_files` (method, line 71) `def test_find_changed_detects_modified_files(self)`
  - `test_find_changed_skips_unchanged_files` (method, line 78) `def test_find_changed_skips_unchanged_files(self)`
  - `test_prune_deleted_removes_ghost_entries` (method, line 85) `def test_prune_deleted_removes_ghost_entries(self)`
  - `test_compute_hashes_batch` (method, line 92) `def test_compute_hashes_batch(self)`
  - `test_nonexistent_file_returns_empty_hash` (method, line 100) `def test_nonexistent_file_returns_empty_hash(self)`
  - `test_save_and_load_analysis_roundtrip` (method, line 109) `def test_save_and_load_analysis_roundtrip(self)`
  - `test_load_missing_analysis_key_returns_none` (method, line 116) `def test_load_missing_analysis_key_returns_none(self)`
  - `test_clear_analysis_specific_key` (method, line 120) `def test_clear_analysis_specific_key(self)`
  - `test_clear_analysis_all_keys` (method, line 127) `def test_clear_analysis_all_keys(self)`
  - `test_has_changed_since_last_analysis_returns_true_on_first_run` (method, line 134) `def test_has_changed_since_last_analysis_returns_true_on_first_run(self)`
  - `test_has_changed_since_last_analysis_returns_false_when_no_changes` (method, line 139) `def test_has_changed_since_last_analysis_returns_false_when_no_changes(self)`
  - `test_has_changed_since_last_analysis_returns_true_when_file_changed` (method, line 147) `def test_has_changed_since_last_analysis_returns_true_when_file_changed(self)`
- Depends on: `readmenator/_cache.py`, `readmenator/_config.py`

## tests/test_components.py
- Doc: Contract tests for the component diagram exporter.
- Layer: testing
- Language: py
- Symbols:
  - `_node` (function, line 17) `def _node(nid)`
  - `_fixture` (function, line 25) `def _fixture()`
  - `TestComponentSchemaContract` (class, line 46) `class TestComponentSchemaContract(TestCase)`
  - `TestComponentRenderContract` (class, line 94) `class TestComponentRenderContract(TestCase)`
  - `TestComponentPageContract` (class, line 140) `class TestComponentPageContract(TestCase)`
  - `TestComponentGalleryContract` (class, line 238) `class TestComponentGalleryContract(TestCase)`
  - `setUp` (method, line 49) `def setUp(self)`
  - `test_top_level_keys_match_schema` (method, line 57) `def test_top_level_keys_match_schema(self)`
  - `test_components_carry_entities_and_file_methods` (method, line 67) `def test_components_carry_entities_and_file_methods(self)`
  - `test_relations_link_components_with_call_sites` (method, line 78) `def test_relations_link_components_with_call_sites(self)`
  - `test_methods_index_keys_use_pipe_separator` (method, line 89) `def test_methods_index_keys_use_pipe_separator(self)`
  - `setUp` (method, line 97) `def setUp(self)`
  - `test_mermaid_matches_layered_style` (method, line 105) `def test_mermaid_matches_layered_style(self)`
  - `test_sanitize_for_mermaid_ids` (method, line 114) `def test_sanitize_for_mermaid_ids(self)`
  - `test_overview_lists_related_entities` (method, line 118) `def test_overview_lists_related_entities(self)`
  - `test_write_emits_analysis_and_pages` (method, line 124) `def test_write_emits_analysis_and_pages(self)`
  - `test_empty_project_yields_empty_diagram` (method, line 133) `def test_empty_project_yields_empty_diagram(self)`
  - `setUp` (method, line 143) `def setUp(self)`
  - `test_page_draws_nodes_edges_and_links` (method, line 152) `def test_page_draws_nodes_edges_and_links(self)`
  - `test_page_nodes_explain_themselves` (method, line 162) `def test_page_nodes_explain_themselves(self)`
  - `test_relations_section_lists_call_evidence` (method, line 171) `def test_relations_section_lists_call_evidence(self)`
  - `test_build_records_layers_hub_and_counts` (method, line 177) `def test_build_records_layers_hub_and_counts(self)`
  - `test_page_has_no_network_requests` (method, line 186) `def test_page_has_no_network_requests(self)`
  - `test_page_escapes_untrusted_text` (method, line 194) `def test_page_escapes_untrusted_text(self)`
  - `test_thumbnail_is_safe_svg` (method, line 204) `def test_thumbnail_is_safe_svg(self)`
  - `test_write_emits_index_page` (method, line 213) `def test_write_emits_index_page(self)`
  - `test_page_script_is_valid_javascript` (method, line 223) `def test_page_script_is_valid_javascript(self)`
  - `_project` (method, line 241) `def _project(self, tmp)`
  - `_config` (method, line 246) `def _config(self)`
  - `test_export_diagrams_links_components_card` (method, line 251) `def test_export_diagrams_links_components_card(self)`
  - `test_export_pages_links_components_relatively` (method, line 266) `def test_export_pages_links_components_relatively(self)`
  - `test_site_copy_has_no_source_links` (method, line 284) `def test_site_copy_has_no_source_links(self)`
  - `test_overview_prefixes_source_links` (method, line 293) `def test_overview_prefixes_source_links(self)`
  - `test_card_skipped_when_disabled` (method, line 305) `def test_card_skipped_when_disabled(self)`
  - `test_docs_collect_components_overview` (method, line 319) `def test_docs_collect_components_overview(self)`
  - `test_nested_relations_roll_up` (method, line 331) `def test_nested_relations_roll_up(self)`
- Depends on: `readmenator/_app.py`, `readmenator/_components.py`, `readmenator/_config.py`, `readmenator/_diagrams.py`, `readmenator/_models.py`

## tests/test_concepts.py
- Doc: test_concept_nouns_map_to_file_sets: Noun tokens become concepts mapping to file sets.
- Layer: testing
- Language: py
- Symbols:
  - `_node` (function, line 8) `def _node(fid, symbols, doc)`
  - `TestConceptGraphContract` (class, line 20) `class TestConceptGraphContract(TestCase)`
  - `test_concept_nouns_map_to_file_sets` (method, line 21) `def test_concept_nouns_map_to_file_sets(self)`
  - `test_concept_verbs_come_from_structural_edges` (method, line 36) `def test_concept_verbs_come_from_structural_edges(self)`
  - `test_concept_atomic_breakdown_splits_camelcase` (method, line 53) `def test_concept_atomic_breakdown_splits_camelcase(self)`
  - `test_concept_deterministic_ordering` (method, line 62) `def test_concept_deterministic_ordering(self)`
  - `test_concept_disabled_returns_empty_graph` (method, line 76) `def test_concept_disabled_returns_empty_graph(self)`
  - `test_concept_dialectic_questions_for_overlap` (method, line 85) `def test_concept_dialectic_questions_for_overlap(self)`
- Depends on: `readmenator/_concepts.py`, `readmenator/_config.py`, `readmenator/_models.py`

## tests/test_config.py
- Layer: testing
- Language: py
- Symbols:
  - `TestConfigContract` (class, line 7) `class TestConfigContract(TestCase)`
  - `test_config_is_immutable` (method, line 8) `def test_config_is_immutable(self)`
  - `test_config_defaults_are_sane` (method, line 13) `def test_config_defaults_are_sane(self)`
  - `test_ignore_dirs_are_comprehensive` (method, line 24) `def test_ignore_dirs_are_comprehensive(self)`
  - `test_plural_map_covers_all_symbol_types` (method, line 30) `def test_plural_map_covers_all_symbol_types(self)`
  - `test_supported_extensions_no_duplicates` (method, line 41) `def test_supported_extensions_no_duplicates(self)`
- Depends on: `readmenator/_config.py`

## tests/test_cpg.py
- Doc: TestCodePropertyGraphContract: Contract: CodePropertyGraph generates valid JSON-LD CPG output.
- Layer: testing
- Language: py
- Symbols:
  - `TestCodePropertyGraphContract` (class, line 11) `class TestCodePropertyGraphContract(TestCase)`
  - `setUp` (method, line 14) `def setUp(self)`
  - `_make_node` (method, line 18) `def _make_node(self, nid, label, lang)`
  - `_make_sym` (method, line 21) `def _make_sym(self, name, kind, line)`
  - `test_generate_returns_valid_json` (method, line 24) `def test_generate_returns_valid_json(self)`
  - `test_generate_includes_node_data` (method, line 33) `def test_generate_includes_node_data(self)`
  - `test_generate_includes_edges` (method, line 49) `def test_generate_includes_edges(self)`
  - `test_generate_includes_metadata` (method, line 61) `def test_generate_includes_metadata(self)`
  - `test_privacy_mode_strips_docs` (method, line 71) `def test_privacy_mode_strips_docs(self)`
  - `test_sha256_hash_included` (method, line 89) `def test_sha256_hash_included(self)`
  - `test_empty_graph_returns_valid_json` (method, line 96) `def test_empty_graph_returns_valid_json(self)`
- Depends on: `readmenator/_config.py`, `readmenator/_cpg.py`, `readmenator/_models.py`

## tests/test_cursorrules.py
- Doc: Contract tests for the CursorRulesGenerator.
- Layer: testing
- Language: py
- Symbols:
  - `TestCursorRulesGeneratorContract` (class, line 18) `class TestCursorRulesGeneratorContract(TestCase)`
  - `setUp` (method, line 21) `def setUp(self)`
  - `test_generate_returns_string` (method, line 25) `def test_generate_returns_string(self)`
  - `test_generate_contains_header` (method, line 29) `def test_generate_contains_header(self)`
  - `test_generate_contains_base_rules` (method, line 33) `def test_generate_contains_base_rules(self)`
  - `test_generate_includes_layer_constraints` (method, line 38) `def test_generate_includes_layer_constraints(self)`
  - `test_generate_includes_god_nodes` (method, line 49) `def test_generate_includes_god_nodes(self)`
  - `test_generate_includes_communities` (method, line 62) `def test_generate_includes_communities(self)`
  - `test_generate_includes_violations` (method, line 82) `def test_generate_includes_violations(self)`
  - `test_generate_limits_violations_to_ten` (method, line 95) `def test_generate_limits_violations_to_ten(self)`
  - `test_generate_writes_file_when_project_root` (method, line 103) `def test_generate_writes_file_when_project_root(self)`
  - `test_generate_idempotent` (method, line 111) `def test_generate_idempotent(self)`
- Depends on: `readmenator/_config.py`, `readmenator/_cursorrules_generator.py`, `readmenator/_models.py`

## tests/test_dataflow.py
- Layer: testing
- Language: py
- Symbols:
  - `_node` (function, line 8) `def _node(node_id, funcs)`
  - `_analyze` (function, line 17) `def _analyze(body)`
  - `TestDataflowContract` (class, line 25) `class TestDataflowContract(TestCase)`
  - `test_config_defaults` (method, line 26) `def test_config_defaults(self)`
  - `test_config_immutable` (method, line 31) `def test_config_immutable(self)`
  - `test_uninit_use_detected` (method, line 37) `def test_uninit_use_detected(self)`
  - `test_initialized_use_clean` (method, line 46) `def test_initialized_use_clean(self)`
  - `test_params_count_as_initialized` (method, line 50) `def test_params_count_as_initialized(self)`
  - `test_scanf_addr_counts_as_init` (method, line 58) `def test_scanf_addr_counts_as_init(self)`
  - `test_dead_store_detected` (method, line 62) `def test_dead_store_detected(self)`
  - `test_read_store_clean` (method, line 67) `def test_read_store_clean(self)`
  - `test_unchecked_alloc_detected` (method, line 71) `def test_unchecked_alloc_detected(self)`
  - `test_checked_alloc_clean` (method, line 78) `def test_checked_alloc_clean(self)`
  - `test_disabled_returns_empty` (method, line 84) `def test_disabled_returns_empty(self)`
  - `test_missing_content_skipped` (method, line 91) `def test_missing_content_skipped(self)`
  - `test_issue_cap_respected` (method, line 96) `def test_issue_cap_respected(self)`
  - `test_plain_assignment_is_not_a_declaration` (method, line 105) `def test_plain_assignment_is_not_a_declaration(self)`
  - `test_subscript_store_counts_as_init` (method, line 110) `def test_subscript_store_counts_as_init(self)`
  - `test_asm_output_counts_as_init` (method, line 116) `def test_asm_output_counts_as_init(self)`
  - `test_fd_lt_zero_counts_as_checked` (method, line 123) `def test_fd_lt_zero_counts_as_checked(self)`
  - `test_map_failed_counts_as_checked` (method, line 129) `def test_map_failed_counts_as_checked(self)`
  - `test_member_null_check_counts` (method, line 136) `def test_member_null_check_counts(self)`
  - `test_loop_carried_var_not_dead` (method, line 144) `def test_loop_carried_var_not_dead(self)`
  - `test_line_numbers_survive_subscript_stores` (method, line 156) `def test_line_numbers_survive_subscript_stores(self)`
  - `test_member_store_not_local_assign` (method, line 167) `def test_member_store_not_local_assign(self)`
  - `test_array_arg_to_filler_counts_as_init` (method, line 173) `def test_array_arg_to_filler_counts_as_init(self)`
  - `test_array_arg_to_readonly_still_uninit` (method, line 181) `def test_array_arg_to_readonly_still_uninit(self)`
  - `test_array_filled_in_decl_init_call` (method, line 188) `def test_array_filled_in_decl_init_call(self)`
  - `test_static_never_uninit` (method, line 196) `def test_static_never_uninit(self)`
  - `test_derived_pointer_not_dead` (method, line 203) `def test_derived_pointer_not_dead(self)`
  - `test_sizeof_is_not_a_read` (method, line 216) `def test_sizeof_is_not_a_read(self)`
  - `test_block_comment_malloc_ignored` (method, line 225) `def test_block_comment_malloc_ignored(self)`
  - `test_address_alias_pointer_not_dead` (method, line 234) `def test_address_alias_pointer_not_dead(self)`
  - `test_array_store_before_read_suppresses_uninit` (method, line 248) `def test_array_store_before_read_suppresses_uninit(self)`
  - `test_same_line_use_not_dead` (method, line 256) `def test_same_line_use_not_dead(self)`
  - `test_same_line_only_assign_is_dead` (method, line 264) `def test_same_line_only_assign_is_dead(self)`
  - `test_alias_pointer_store_initializes_array` (method, line 272) `def test_alias_pointer_store_initializes_array(self)`
  - `test_inline_alias_fill_suppresses_uninit` (method, line 281) `def test_inline_alias_fill_suppresses_uninit(self)`
  - `test_function_pointer_call_counts_as_use` (method, line 290) `def test_function_pointer_call_counts_as_use(self)`
  - `test_plain_call_is_not_a_local_use` (method, line 298) `def test_plain_call_is_not_a_local_use(self)`
  - `test_local_struct_does_not_truncate_span` (method, line 305) `def test_local_struct_does_not_truncate_span(self)`
  - `test_file_scope_symbol_still_bounds_span` (method, line 329) `def test_file_scope_symbol_still_bounds_span(self)`
  - `test_url_string_does_not_truncate_line` (method, line 344) `def test_url_string_does_not_truncate_line(self)`
  - `test_assert_macro_counts_as_null_check` (method, line 352) `def test_assert_macro_counts_as_null_check(self)`
  - `test_multiline_call_assigns_array_arg` (method, line 360) `def test_multiline_call_assigns_array_arg(self)`
  - `test_address_taken_suppresses_dead_store` (method, line 369) `def test_address_taken_suppresses_dead_store(self)`
  - `test_member_store_initializes_base` (method, line 377) `def test_member_store_initializes_base(self)`
- Depends on: `readmenator/_config.py`, `readmenator/_dataflow.py`, `readmenator/_models.py`

## tests/test_dead_code.py
- Doc: Contract tests for the DeadCodeStripper.
- Layer: testing
- Language: py
- Symbols:
  - `TestDeadCodeStripperContract` (class, line 16) `class TestDeadCodeStripperContract(TestCase)`
  - `setUp` (method, line 19) `def setUp(self)`
  - `_make_symbol` (method, line 23) `def _make_symbol(self, name, kind)`
  - `_make_node` (method, line 26) `def _make_node(self, nid, symbols)`
  - `_make_edge` (method, line 35) `def _make_edge(self, src, tgt)`
  - `test_identify_empty_graph_returns_empty` (method, line 38) `def test_identify_empty_graph_returns_empty(self)`
  - `test_identify_finds_dead_symbol` (method, line 42) `def test_identify_finds_dead_symbol(self)`
  - `test_identify_excludes_entry_points` (method, line 53) `def test_identify_excludes_entry_points(self)`
  - `test_identify_excludes_app_entry_point` (method, line 61) `def test_identify_excludes_app_entry_point(self)`
  - `test_identify_excludes_init_entry_point` (method, line 69) `def test_identify_excludes_init_entry_point(self)`
  - `test_identify_recommends_review_for_classes` (method, line 77) `def test_identify_recommends_review_for_classes(self)`
  - `test_identify_recommends_trash_for_functions` (method, line 85) `def test_identify_recommends_trash_for_functions(self)`
  - `test_identify_recommends_trash_for_variables` (method, line 93) `def test_identify_recommends_trash_for_variables(self)`
  - `test_all_symbols_imported_returns_empty` (method, line 101) `def test_all_symbols_imported_returns_empty(self)`
  - `test_reports_sorted_by_file_path` (method, line 113) `def test_reports_sorted_by_file_path(self)`
- Depends on: `readmenator/_config.py`, `readmenator/_dead_code.py`, `readmenator/_models.py`


Next: [KB_tests_p2.md](KB_tests_p2.md)
