deepdoc 0.4.0__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {deepdoc-0.4.0 → deepdoc-0.4.2}/PKG-INFO +1 -1
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/generator/post_processors.py +8 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/falcon.py +22 -2
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/topology.py +100 -6
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/__init__.py +0 -1
- deepdoc-0.4.2/deepdoc/site/builder/next_template/app/ask/page.tsx +1168 -0
- deepdoc-0.4.2/deepdoc/site/builder/next_template/app/components/mermaid-runner.tsx +19 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/layout.tsx +2 -9
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/next.config.mjs +3 -1
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/package.json +2 -1
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc.egg-info/PKG-INFO +1 -1
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc.egg-info/SOURCES.txt +1 -3
- {deepdoc-0.4.0 → deepdoc-0.4.2}/pyproject.toml +1 -1
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_query.py +3 -3
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_framework_support.py +25 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_planner_granularity.py +0 -47
- deepdoc-0.4.0/deepdoc/site/builder/mkdocs_builder.py +0 -2150
- deepdoc-0.4.0/deepdoc/site/builder/next_template/app/[[...slug]]/page.tsx +0 -37
- deepdoc-0.4.0/deepdoc/site/builder/next_template/app/ask/page.tsx +0 -630
- deepdoc-0.4.0/tests/test_mkdocs_builder.py +0 -920
- {deepdoc-0.4.0 → deepdoc-0.4.2}/LICENSE +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/README.md +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/__main__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/benchmark_v2.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/call_graph.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/changelog_writer.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/answer_mixin.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/chunker.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/constants.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/deep_research.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/docs_summary.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/embeddings.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/indexer.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/linking.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/live_fallback_mixin.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/persistence.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/providers.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/retrieval_mixin.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/routes.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/scaffold.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/service.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/settings.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/source_archive.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/symbol_index.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/chatbot/types.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/cli.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/config.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/generator/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/generator/consistency.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/generator/evidence.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/generator/generation.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/generator/validation.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/llm/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/llm/client.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/llm/json_utils.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/llm/litellm_compat.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/llm/retry.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/manifest.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/openapi.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/api_detector.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/base.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/go_parser.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/js_ts_parser.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/php_parser.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/python_parser.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/registry.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/base.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/common.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/detector.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/django.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/express.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/fastify.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/go.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/js_shared.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/laravel.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/nestjs.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/python_shared.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/registry.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/routes/repo_resolver.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/parser/vue_parser.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/persistence_v2.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/pipeline_v2.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/bucket_injection.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/bucket_refinement.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/common.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/endpoint_refs.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/engine.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/flow_candidates.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/heuristics.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/nav_shaping.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/specializations.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/planner/utils.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/prompts/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/prompts/bucket_types.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/prompts/page_types.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/prompts/selectors.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/prompts/system.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/prompts/update.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/py.typed +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/artifacts.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/clustering.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/common.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/database.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/endpoints.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/integrations.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/runtime.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/scanner/utils.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/__init__.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/common.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/mdx_utils.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_builder.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/.gitignore +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/(main)/[[...slug]]/page.tsx +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/(main)/layout.tsx +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/ask/layout.tsx +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/app/globals.css +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/components/chatbot.tsx +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/deepdoc.config.json +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/lib/config.ts +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/lib/docs.ts +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/lib/nav.ts +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/next_template/tsconfig.json +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/vendor/highlight.min.js +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/vendor/hljs-theme.css +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/site/builder/vendor/markdown-it.min.js +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/smart_update_v2.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/source_metadata.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/updater_v2.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc/v2_models.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc.egg-info/dependency_links.txt +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc.egg-info/entry_points.txt +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc.egg-info/requires.txt +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/deepdoc.egg-info/top_level.txt +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/setup.cfg +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_benchmark_scorecard.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_call_graph.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_changelog.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_config.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_embeddings.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_eval.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_index.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_persistence.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_providers.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_relationship.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_scaffold.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_chatbot_source_archive.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_classify.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_cli_generate.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_cli_serve.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_cli_update.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_consistency_pass.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_flow_candidates.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_framework_fixtures.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_generation_evidence.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_internal_docs_metadata.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_litellm_compat.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_llm_json_utils.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_llm_retry.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_next_builder.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_parallel_pipeline.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_parser_ranges.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_planner_consolidation.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_route_registry.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_runtime_scan.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_smart_update.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_stale.py +0 -0
- {deepdoc-0.4.0 → deepdoc-0.4.2}/tests/test_state.py +0 -0
|
@@ -139,6 +139,14 @@ def _fix_mermaid_diagram(diagram: str) -> str:
|
|
|
139
139
|
line,
|
|
140
140
|
)
|
|
141
141
|
|
|
142
|
+
# Fix: backticks inside double-quoted node labels break Mermaid 11+
|
|
143
|
+
# e.g. ["foo() (`bar.py:10`)"] → ["foo() (bar.py:10)"]
|
|
144
|
+
line = re.sub(
|
|
145
|
+
r'("(?:[^"\\]|\\.)*")',
|
|
146
|
+
lambda m: m.group(0).replace('`', ''),
|
|
147
|
+
line,
|
|
148
|
+
)
|
|
149
|
+
|
|
142
150
|
# Fix: Node labels with colons not in quotes
|
|
143
151
|
line = re.sub(
|
|
144
152
|
r'\[([^\]"]*:[^\]"]*)\]',
|
|
@@ -114,8 +114,20 @@ def _fallback_falcon_path(path_expr: str) -> str:
|
|
|
114
114
|
return "".join(string_parts)
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
def
|
|
118
|
-
"""
|
|
117
|
+
def _base_class_name(class_body: str) -> str:
|
|
118
|
+
"""Return the first base class name from a class definition line, or ''."""
|
|
119
|
+
m = re.match(r"class\s+\w+\s*\(\s*([A-Za-z_]\w*)", class_body)
|
|
120
|
+
return m.group(1) if m else ""
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def find_falcon_responders(
|
|
124
|
+
content: str,
|
|
125
|
+
class_name: str,
|
|
126
|
+
_visited: frozenset[str] | None = None,
|
|
127
|
+
) -> list[str]:
|
|
128
|
+
"""Find all on_* methods in a Falcon resource class, including inherited ones."""
|
|
129
|
+
if _visited is None:
|
|
130
|
+
_visited = frozenset()
|
|
119
131
|
methods: list[str] = []
|
|
120
132
|
class_pattern = re.compile(
|
|
121
133
|
rf"class\s+{re.escape(class_name)}\b.*?(?=\nclass\s|\Z)",
|
|
@@ -128,6 +140,14 @@ def find_falcon_responders(content: str, class_name: str) -> list[str]:
|
|
|
128
140
|
method_name = match.group(1)
|
|
129
141
|
http_method = method_name.replace("on_", "").upper()
|
|
130
142
|
methods.append(http_method)
|
|
143
|
+
base = _base_class_name(class_body)
|
|
144
|
+
if base and base not in _visited:
|
|
145
|
+
inherited = find_falcon_responders(
|
|
146
|
+
content, base, _visited | {class_name}
|
|
147
|
+
)
|
|
148
|
+
for m in inherited:
|
|
149
|
+
if m not in methods:
|
|
150
|
+
methods.append(m)
|
|
131
151
|
return methods
|
|
132
152
|
|
|
133
153
|
|
|
@@ -41,6 +41,11 @@ _MAX_CLUSTER_DEPTH = 4
|
|
|
41
41
|
# docs/planner_tuning.md.
|
|
42
42
|
_MERGE_JACCARD = 0.60
|
|
43
43
|
|
|
44
|
+
# Two clusters whose cross-call ratio exceeds this threshold merge even when
|
|
45
|
+
# their file-Jaccard is low. Prevents tightly coupled micro-clusters from
|
|
46
|
+
# getting separate doc pages.
|
|
47
|
+
_CROSS_EDGE_DENSITY = 0.35
|
|
48
|
+
|
|
44
49
|
|
|
45
50
|
@dataclass
|
|
46
51
|
class TopologyCluster:
|
|
@@ -124,8 +129,16 @@ def build_topology_map(scan: "RepoScan") -> TopologyMap:
|
|
|
124
129
|
}
|
|
125
130
|
|
|
126
131
|
# ── 3. Foundational files ─────────────────────────────────────────────
|
|
132
|
+
# Augment call indegree with import indegree so that heavily-imported files
|
|
133
|
+
# (models.py, constants.py, exceptions.py) are treated as foundational even
|
|
134
|
+
# when they have few direct call-graph edges.
|
|
135
|
+
import_indegree, file_imports = _build_import_maps(scan, repo_files)
|
|
136
|
+
effective_indegree = {
|
|
137
|
+
f: max(file_indegree.get(f, 0), import_indegree.get(f, 0))
|
|
138
|
+
for f in repo_files
|
|
139
|
+
}
|
|
127
140
|
threshold = max(3, int(len(repo_files) * _FOUNDATIONAL_FRACTION))
|
|
128
|
-
foundational_set = {f for f, deg in
|
|
141
|
+
foundational_set = {f for f, deg in effective_indegree.items() if deg >= threshold}
|
|
129
142
|
|
|
130
143
|
# ── 4. Entry-point files ──────────────────────────────────────────────
|
|
131
144
|
entry_point_files: set[str] = set()
|
|
@@ -211,7 +224,9 @@ def build_topology_map(scan: "RepoScan") -> TopologyMap:
|
|
|
211
224
|
if f not in file_cluster_id and f not in foundational_set
|
|
212
225
|
]
|
|
213
226
|
for f in sorted(unassigned):
|
|
214
|
-
best_cid = _best_cluster_for_orphan(
|
|
227
|
+
best_cid = _best_cluster_for_orphan(
|
|
228
|
+
f, file_calls, file_called_by, proto, file_imports
|
|
229
|
+
)
|
|
215
230
|
if best_cid:
|
|
216
231
|
file_cluster_id[f] = best_cid
|
|
217
232
|
proto[best_cid].add(f)
|
|
@@ -221,7 +236,7 @@ def build_topology_map(scan: "RepoScan") -> TopologyMap:
|
|
|
221
236
|
proto[fallback].add(f)
|
|
222
237
|
|
|
223
238
|
# ── 7. Merge high-overlap clusters ───────────────────────────────────
|
|
224
|
-
merged = _merge_proto_clusters(proto, file_cluster_id)
|
|
239
|
+
merged = _merge_proto_clusters(proto, file_cluster_id, file_calls)
|
|
225
240
|
|
|
226
241
|
# ── 8. Build TopologyCluster objects ─────────────────────────────────
|
|
227
242
|
clusters: list[TopologyCluster] = []
|
|
@@ -313,10 +328,12 @@ def _empty_map() -> TopologyMap:
|
|
|
313
328
|
def _merge_proto_clusters(
|
|
314
329
|
proto: dict[str, set[str]],
|
|
315
330
|
file_cluster_id: dict[str, str],
|
|
331
|
+
file_calls: dict[str, set[str]] | None = None,
|
|
316
332
|
) -> dict[str, set[str]]:
|
|
317
|
-
"""Merge clusters whose file Jaccard overlap
|
|
333
|
+
"""Merge clusters whose file Jaccard overlap or cross-call density is high."""
|
|
318
334
|
cluster_ids = list(proto.keys())
|
|
319
335
|
parent: dict[str, str] = {}
|
|
336
|
+
_fc = file_calls or {}
|
|
320
337
|
|
|
321
338
|
def find(cid: str) -> str:
|
|
322
339
|
while cid in parent:
|
|
@@ -347,6 +364,12 @@ def _merge_proto_clusters(
|
|
|
347
364
|
jaccard = len(si & sj) / len(si | sj)
|
|
348
365
|
if jaccard >= _MERGE_JACCARD:
|
|
349
366
|
union(ci, cj)
|
|
367
|
+
continue
|
|
368
|
+
# Also merge when cross-call traffic between the two clusters is dense
|
|
369
|
+
cross = sum(1 for f in si for c in _fc.get(f, set()) if c in sj)
|
|
370
|
+
cross += sum(1 for f in sj for c in _fc.get(f, set()) if c in si)
|
|
371
|
+
if cross and cross / min(len(si), len(sj)) >= _CROSS_EDGE_DENSITY:
|
|
372
|
+
union(ci, cj)
|
|
350
373
|
|
|
351
374
|
# Re-assign file_cluster_id to canonical roots
|
|
352
375
|
for f in list(file_cluster_id.keys()):
|
|
@@ -355,14 +378,79 @@ def _merge_proto_clusters(
|
|
|
355
378
|
return {find(cid): files for cid, files in proto.items() if files}
|
|
356
379
|
|
|
357
380
|
|
|
381
|
+
def _build_import_maps(
|
|
382
|
+
scan: "RepoScan",
|
|
383
|
+
repo_files: set[str],
|
|
384
|
+
) -> tuple[dict[str, int], dict[str, set[str]]]:
|
|
385
|
+
"""Build import indegree and file→imported-files adjacency in one pass.
|
|
386
|
+
|
|
387
|
+
Resolves relative and absolute imports to repo file paths; ignores
|
|
388
|
+
third-party/stdlib. Returns (indegree, file_imports) where:
|
|
389
|
+
- indegree[f] = number of distinct repo files that import f
|
|
390
|
+
- file_imports[f] = set of repo files that f imports
|
|
391
|
+
"""
|
|
392
|
+
indegree: dict[str, int] = defaultdict(int)
|
|
393
|
+
file_imports: dict[str, set[str]] = defaultdict(set)
|
|
394
|
+
|
|
395
|
+
_suffix_index: dict[str, list[str]] = defaultdict(list)
|
|
396
|
+
for rp in repo_files:
|
|
397
|
+
no_ext = re.sub(r"\.[^/]+$", "", rp)
|
|
398
|
+
_suffix_index[no_ext].append(rp)
|
|
399
|
+
_suffix_index[no_ext.replace("/", ".")].append(rp)
|
|
400
|
+
|
|
401
|
+
import_re = re.compile(
|
|
402
|
+
r"^(?:from\s+([\w.]+)\s+import|import\s+([\w.]+))", re.MULTILINE
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
for src_path, pf in scan.parsed_files.items():
|
|
406
|
+
if src_path not in repo_files:
|
|
407
|
+
continue
|
|
408
|
+
pkg_dir = src_path.rsplit("/", 1)[0] if "/" in src_path else ""
|
|
409
|
+
|
|
410
|
+
for imp_str in pf.imports:
|
|
411
|
+
m = import_re.match(imp_str.strip())
|
|
412
|
+
if not m:
|
|
413
|
+
continue
|
|
414
|
+
module = (m.group(1) or m.group(2) or "").strip()
|
|
415
|
+
if not module:
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
candidates: list[str] = []
|
|
419
|
+
|
|
420
|
+
if module.startswith("."):
|
|
421
|
+
dots = len(module) - len(module.lstrip("."))
|
|
422
|
+
rel_mod = module[dots:].replace(".", "/")
|
|
423
|
+
base = pkg_dir
|
|
424
|
+
for _ in range(dots - 1):
|
|
425
|
+
base = base.rsplit("/", 1)[0] if "/" in base else ""
|
|
426
|
+
rel_path = f"{base}/{rel_mod}".lstrip("/") if rel_mod else base
|
|
427
|
+
for ext in (".py", "/__init__.py"):
|
|
428
|
+
candidates.append(rel_path + ext)
|
|
429
|
+
else:
|
|
430
|
+
as_path = module.replace(".", "/")
|
|
431
|
+
for ext in (".py", "/__init__.py"):
|
|
432
|
+
candidates.append(as_path + ext)
|
|
433
|
+
candidates.extend(_suffix_index.get(module, []))
|
|
434
|
+
candidates.extend(_suffix_index.get(as_path, []))
|
|
435
|
+
|
|
436
|
+
for candidate in candidates:
|
|
437
|
+
if candidate in repo_files and candidate != src_path:
|
|
438
|
+
indegree[candidate] += 1
|
|
439
|
+
file_imports[src_path].add(candidate)
|
|
440
|
+
break
|
|
441
|
+
|
|
442
|
+
return dict(indegree), dict(file_imports)
|
|
443
|
+
|
|
444
|
+
|
|
358
445
|
def _best_cluster_for_orphan(
|
|
359
446
|
file_path: str,
|
|
360
447
|
file_calls: dict[str, set[str]],
|
|
361
448
|
file_called_by: dict[str, set[str]],
|
|
362
449
|
proto: dict[str, set[str]],
|
|
450
|
+
import_targets: dict[str, set[str]] | None = None,
|
|
363
451
|
) -> str | None:
|
|
364
|
-
"""Find the cluster an unassigned file has the most
|
|
365
|
-
scores: dict[str,
|
|
452
|
+
"""Find the cluster an unassigned file has the most relationships with."""
|
|
453
|
+
scores: dict[str, float] = defaultdict(float)
|
|
366
454
|
cluster_file_index: dict[str, str] = {}
|
|
367
455
|
for cid, files in proto.items():
|
|
368
456
|
for f in files:
|
|
@@ -378,6 +466,12 @@ def _best_cluster_for_orphan(
|
|
|
378
466
|
if cid:
|
|
379
467
|
scores[cid] += 1
|
|
380
468
|
|
|
469
|
+
# Import edges provide a weaker signal than call edges
|
|
470
|
+
for imp_target in (import_targets or {}).get(file_path, set()):
|
|
471
|
+
cid = cluster_file_index.get(imp_target)
|
|
472
|
+
if cid:
|
|
473
|
+
scores[cid] += 0.5
|
|
474
|
+
|
|
381
475
|
if not scores:
|
|
382
476
|
return None
|
|
383
477
|
return max(scores, key=lambda c: scores[c])
|
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
from .next_builder import build_next_from_plan
|
|
2
|
-
from .mkdocs_builder import build_mkdocs_from_plan # kept for compatibility
|
|
3
2
|
from .mdx_utils import _first_mdx_heading, _split_leading_frontmatter, _frontmatter_has_yaml_fields, _ensure_md_frontmatter
|
|
4
3
|
from .common import *
|