pystdoc 0.8.2__tar.gz → 0.8.4__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.
- {pystdoc-0.8.2/src/pystdoc.egg-info → pystdoc-0.8.4}/PKG-INFO +2 -2
- {pystdoc-0.8.2 → pystdoc-0.8.4}/README.md +1 -1
- {pystdoc-0.8.2 → pystdoc-0.8.4}/pyproject.toml +1 -1
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/__init__.py +1 -1
- pystdoc-0.8.4/src/pystdoc/call_graph.py +77 -0
- pystdoc-0.8.4/src/pystdoc/call_graph_nodes.py +77 -0
- pystdoc-0.8.4/src/pystdoc/callee_summary.py +66 -0
- pystdoc-0.8.4/src/pystdoc/doc_labels.py +171 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/doc_writer.py +144 -47
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/engine.py +14 -1
- pystdoc-0.8.4/src/pystdoc/level_graph.py +152 -0
- pystdoc-0.8.4/src/pystdoc/scc.py +54 -0
- pystdoc-0.8.4/src/pystdoc/var_linker.py +118 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4/src/pystdoc.egg-info}/PKG-INFO +2 -2
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc.egg-info/SOURCES.txt +8 -0
- pystdoc-0.8.4/tests/test_call_graph_split.py +137 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_config.py +91 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_docgen.py +15 -2
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_live_server_100.py +2 -2
- pystdoc-0.8.4/tests/test_real_litert_server.py +240 -0
- pystdoc-0.8.2/src/pystdoc/call_graph.py +0 -344
- {pystdoc-0.8.2 → pystdoc-0.8.4}/LICENSE +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/setup.cfg +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/__main__.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/adapters/__init__.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/adapters/base.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/adapters/clang_adapter.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/adapters/generic_adapter.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/adapters/java_adapter.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/adapters/kotlin_adapter.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/adapters/python_adapter.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/cache.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/cli.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/compilation_db.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/config.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/db.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/design_engine.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/hasher.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/interrupt.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/llm_client.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/mcp_server.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/parser_clang.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/parser_generic.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/parser_java.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/parser_kotlin.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/parser_python.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/perf.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/progress.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/query.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/report_engine.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/scanner.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/similarity.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/symbols.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/ui_detector.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc/watcher.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc.egg-info/dependency_links.txt +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc.egg-info/entry_points.txt +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc.egg-info/requires.txt +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/src/pystdoc.egg-info/top_level.txt +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_adapters.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_cli.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_coverage_boost.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_design_engine_full.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_designgen.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_engine.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_engine_full.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_final_100.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_interrupt.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_llm_client.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_mcp_server.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_parser_generic.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_parser_java.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_parser_kotlin.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_pep8.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_perf.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_progress.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_query.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_reduce_cache.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_reportgen.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_ui_detector.py +0 -0
- {pystdoc-0.8.2 → pystdoc-0.8.4}/tests/test_watcher.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pystdoc
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.4
|
|
4
4
|
Summary: Python Structural & Topological Documentation Engine (pystdoc): High-precision, LLM-powered hierarchical codebase and architecture documentation generator.
|
|
5
5
|
Author: tab4moji
|
|
6
6
|
License: MIT
|
|
@@ -78,7 +78,7 @@ your_project/
|
|
|
78
78
|
- Evaluates call graphs in $O(V+E)$ linear time.
|
|
79
79
|
- Automatically breaks cyclic mutual recursions and organizes code symbols into dependency-safe execution levels.
|
|
80
80
|
- Level-by-level parallel LLM execution guarantees context-rich bottom-up summaries without race conditions.
|
|
81
|
-
2. **High-Speed Incremental Sync & Semantic Change Propagation (v0.8.0 - v0.8.
|
|
81
|
+
2. **High-Speed Incremental Sync & Semantic Change Propagation (v0.8.0 - v0.8.4)**:
|
|
82
82
|
- **Selective Bottom-Up Propagation & Immediate Snippet Flush**: Detects AST and logic-level code differences. If regenerated specifications remain semantically unchanged, cascading LLM re-analysis terminates early while Markdown documents (source code snippets, line ranges, caller/callee references) are immediately updated with latest code. Signature, kind, and callee alterations are guaranteed to propagate upstream.
|
|
83
83
|
- **Content-Defined Map-Reduce Caching**: Intermediate summaries in architecture synthesis (`designgen`) are chunked using stable hash boundaries and cached per group in SQLite, ensuring that only groups containing edited functions are re-summarized.
|
|
84
84
|
- **Architectural Entry-Point Filtering**: For system execution modeling (`execution_model.md`), key architectural functions (entry points, CLI routers, and core dispatchers) are automatically filtered and passed directly into the LLM context, bypassing heavy multi-step Map-Reduce loops and speeding up generation by up to 90%+.
|
|
@@ -49,7 +49,7 @@ your_project/
|
|
|
49
49
|
- Evaluates call graphs in $O(V+E)$ linear time.
|
|
50
50
|
- Automatically breaks cyclic mutual recursions and organizes code symbols into dependency-safe execution levels.
|
|
51
51
|
- Level-by-level parallel LLM execution guarantees context-rich bottom-up summaries without race conditions.
|
|
52
|
-
2. **High-Speed Incremental Sync & Semantic Change Propagation (v0.8.0 - v0.8.
|
|
52
|
+
2. **High-Speed Incremental Sync & Semantic Change Propagation (v0.8.0 - v0.8.4)**:
|
|
53
53
|
- **Selective Bottom-Up Propagation & Immediate Snippet Flush**: Detects AST and logic-level code differences. If regenerated specifications remain semantically unchanged, cascading LLM re-analysis terminates early while Markdown documents (source code snippets, line ranges, caller/callee references) are immediately updated with latest code. Signature, kind, and callee alterations are guaranteed to propagate upstream.
|
|
54
54
|
- **Content-Defined Map-Reduce Caching**: Intermediate summaries in architecture synthesis (`designgen`) are chunked using stable hash boundaries and cached per group in SQLite, ensuring that only groups containing edited functions are re-summarized.
|
|
55
55
|
- **Architectural Entry-Point Filtering**: For system execution modeling (`execution_model.md`), key architectural functions (entry points, CLI routers, and core dispatchers) are automatically filtered and passed directly into the LLM context, bypassing heavy multi-step Map-Reduce loops and speeding up generation by up to 90%+.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pystdoc"
|
|
7
|
-
version = "0.8.
|
|
7
|
+
version = "0.8.4"
|
|
8
8
|
description = "Python Structural & Topological Documentation Engine (pystdoc): High-precision, LLM-powered hierarchical codebase and architecture documentation generator."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Call graph facade: Level DAG ordering and backward-compatible exports.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Partition symbol nodes into dependency-safe levels (callees before
|
|
5
|
+
callers, SCC groups together) for level-by-level parallel LLM runs.
|
|
6
|
+
The implementation is split across small modules; the public names
|
|
7
|
+
below are re-exported so existing imports keep working:
|
|
8
|
+
|
|
9
|
+
- call_graph_nodes : SymbolNode, flatten_symbols
|
|
10
|
+
- var_linker : link_variables_to_functions
|
|
11
|
+
- scc : tarjan_scc
|
|
12
|
+
- level_graph : ordering helpers
|
|
13
|
+
- callee_summary : build_callee_context_summary
|
|
14
|
+
|
|
15
|
+
Change history:
|
|
16
|
+
1. 2026-11-02 (v0.8.3): Split into the modules above. Fixed the
|
|
17
|
+
redundant SCC re-queueing in ranking (see level_graph.py).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import List
|
|
21
|
+
|
|
22
|
+
from pystdoc.callee_summary import build_callee_context_summary
|
|
23
|
+
from pystdoc.call_graph_nodes import SymbolNode, flatten_symbols
|
|
24
|
+
from pystdoc.level_graph import (
|
|
25
|
+
assemble_levels,
|
|
26
|
+
assign_scc_groups,
|
|
27
|
+
build_callee_edges,
|
|
28
|
+
build_condensation,
|
|
29
|
+
index_nodes,
|
|
30
|
+
rank_sccs,
|
|
31
|
+
)
|
|
32
|
+
from pystdoc.scc import tarjan_scc
|
|
33
|
+
from pystdoc.symbols import get_kind_prefix
|
|
34
|
+
from pystdoc.var_linker import link_variables_to_functions
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"SymbolNode",
|
|
38
|
+
"flatten_symbols",
|
|
39
|
+
"link_variables_to_functions",
|
|
40
|
+
"tarjan_scc",
|
|
41
|
+
"order_symbols_by_levels",
|
|
42
|
+
"order_symbols_bottom_up",
|
|
43
|
+
"build_callee_context_summary",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def order_symbols_by_levels(
|
|
48
|
+
nodes: List[SymbolNode],
|
|
49
|
+
) -> List[List[SymbolNode]]:
|
|
50
|
+
"""Partition symbol nodes into strictly dependency-safe levels."""
|
|
51
|
+
id_to_node, fqdn_to_ids, short_to_ids = index_nodes(nodes)
|
|
52
|
+
caller_to_callees = build_callee_edges(
|
|
53
|
+
nodes, id_to_node, fqdn_to_ids, short_to_ids
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
base_nodes = [n for n in nodes if get_kind_prefix(n.symbol.kind) != "fn"]
|
|
57
|
+
for node in base_nodes:
|
|
58
|
+
node.dag_level = 0
|
|
59
|
+
fn_ids = [
|
|
60
|
+
n.unique_id for n in nodes if get_kind_prefix(n.symbol.kind) == "fn"
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
sccs = tarjan_scc(fn_ids, caller_to_callees)
|
|
64
|
+
node_to_scc_idx = assign_scc_groups(sccs, id_to_node)
|
|
65
|
+
scc_callees, scc_callers = build_condensation(
|
|
66
|
+
node_to_scc_idx, caller_to_callees
|
|
67
|
+
)
|
|
68
|
+
scc_rank = rank_sccs(len(sccs), scc_callees, scc_callers)
|
|
69
|
+
return assemble_levels(base_nodes, sccs, scc_rank, id_to_node)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def order_symbols_bottom_up(nodes: List[SymbolNode]) -> List[SymbolNode]:
|
|
73
|
+
"""Flattened bottom-up order."""
|
|
74
|
+
flat: List[SymbolNode] = []
|
|
75
|
+
for level in order_symbols_by_levels(nodes):
|
|
76
|
+
flat.extend(level)
|
|
77
|
+
return flat
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""SymbolNode data structure and symbol flattening.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Define the flat graph node (SymbolNode) and convert a hierarchical
|
|
5
|
+
Symbol tree into a flat list of nodes with unique IDs and FQDNs.
|
|
6
|
+
|
|
7
|
+
Change history:
|
|
8
|
+
1. 2026-11-02 (v0.8.3): Split out of call_graph.py (no logic change).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from collections import defaultdict
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Dict, List, Optional, Set
|
|
15
|
+
|
|
16
|
+
from pystdoc.symbols import Symbol, get_kind_prefix
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class SymbolNode:
|
|
21
|
+
symbol: Symbol
|
|
22
|
+
rel_path: Path
|
|
23
|
+
full_path: Path
|
|
24
|
+
unique_id: str
|
|
25
|
+
fqdn: str = ""
|
|
26
|
+
direct_callee_ids: Set[str] = field(default_factory=set)
|
|
27
|
+
direct_caller_ids: Set[str] = field(default_factory=set)
|
|
28
|
+
scc_group_ids: List[str] = field(default_factory=list)
|
|
29
|
+
dag_level: int = 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def flatten_symbols(
|
|
33
|
+
symbols: List[Symbol],
|
|
34
|
+
rel_path: Path,
|
|
35
|
+
full_path: Path,
|
|
36
|
+
prefix: str = "",
|
|
37
|
+
seen_ids: Optional[Dict[str, int]] = None,
|
|
38
|
+
) -> List[SymbolNode]:
|
|
39
|
+
"""Recursively collect symbols into flat list with FQDN and prefixes."""
|
|
40
|
+
if seen_ids is None:
|
|
41
|
+
seen_ids = defaultdict(int)
|
|
42
|
+
nodes: List[SymbolNode] = []
|
|
43
|
+
for sym in symbols:
|
|
44
|
+
k_prefix = get_kind_prefix(sym.kind)
|
|
45
|
+
raw_name = f"{prefix}{sym.name}" if prefix else sym.name
|
|
46
|
+
identifier = f"{k_prefix}.{raw_name}"
|
|
47
|
+
base_uid = f"{rel_path.as_posix()}::{identifier}"
|
|
48
|
+
seen_ids[base_uid] += 1
|
|
49
|
+
if seen_ids[base_uid] == 1:
|
|
50
|
+
uid = base_uid
|
|
51
|
+
else:
|
|
52
|
+
uid = f"{base_uid}#{seen_ids[base_uid]}"
|
|
53
|
+
|
|
54
|
+
base_mod = rel_path.with_suffix("").as_posix().replace("/", ".")
|
|
55
|
+
fqdn = sym.fqdn or f"{base_mod}.{raw_name}"
|
|
56
|
+
|
|
57
|
+
node = SymbolNode(
|
|
58
|
+
symbol=sym,
|
|
59
|
+
rel_path=rel_path,
|
|
60
|
+
full_path=full_path,
|
|
61
|
+
unique_id=uid,
|
|
62
|
+
fqdn=fqdn,
|
|
63
|
+
direct_callee_ids=set(),
|
|
64
|
+
scc_group_ids=[],
|
|
65
|
+
dag_level=0,
|
|
66
|
+
)
|
|
67
|
+
nodes.append(node)
|
|
68
|
+
if sym.children:
|
|
69
|
+
child_nodes = flatten_symbols(
|
|
70
|
+
sym.children,
|
|
71
|
+
rel_path,
|
|
72
|
+
full_path,
|
|
73
|
+
prefix=f"{raw_name}.",
|
|
74
|
+
seen_ids=seen_ids,
|
|
75
|
+
)
|
|
76
|
+
nodes.extend(child_nodes)
|
|
77
|
+
return nodes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Callee context summary builder for LLM prompts.
|
|
2
|
+
|
|
3
|
+
Purpose:
|
|
4
|
+
Build a text summary of directly called functions and mutual
|
|
5
|
+
recursion peers of a symbol node, used as bottom-up LLM context.
|
|
6
|
+
|
|
7
|
+
Change history:
|
|
8
|
+
1. 2026-11-02 (v0.8.3): Split out of call_graph.py (no logic change).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Dict, List
|
|
12
|
+
|
|
13
|
+
from pystdoc.call_graph_nodes import SymbolNode
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def build_callee_context_summary(
|
|
17
|
+
node: SymbolNode,
|
|
18
|
+
resolved_symbols: Dict[str, SymbolNode],
|
|
19
|
+
) -> str:
|
|
20
|
+
"""Build summary text of called functions and recursion peers for LLM."""
|
|
21
|
+
lines: List[str] = []
|
|
22
|
+
|
|
23
|
+
if node.scc_group_ids:
|
|
24
|
+
peer_names = []
|
|
25
|
+
for peer_id in node.scc_group_ids:
|
|
26
|
+
peer_node = resolved_symbols.get(peer_id)
|
|
27
|
+
if peer_node:
|
|
28
|
+
peer_names.append(
|
|
29
|
+
f"`{peer_node.symbol.name}` ({peer_node.rel_path.name})"
|
|
30
|
+
)
|
|
31
|
+
if peer_names:
|
|
32
|
+
lines.append("[Mutual Recursion Group]:")
|
|
33
|
+
lines.append(
|
|
34
|
+
f"- Note: This function operates in mutual recursion "
|
|
35
|
+
f"with {', '.join(peer_names)}."
|
|
36
|
+
)
|
|
37
|
+
lines.append("")
|
|
38
|
+
|
|
39
|
+
if node.direct_callee_ids:
|
|
40
|
+
lines.append("[Direct Callees Summary]:")
|
|
41
|
+
for callee_id in sorted(node.direct_callee_ids):
|
|
42
|
+
callee_node = resolved_symbols.get(callee_id)
|
|
43
|
+
if callee_node:
|
|
44
|
+
sym = callee_node.symbol
|
|
45
|
+
purpose = sym.purpose or "Executes operation"
|
|
46
|
+
params_str = ", ".join(
|
|
47
|
+
f"{p.name}: {p.type_hint}" for p in sym.parameters
|
|
48
|
+
)
|
|
49
|
+
inputs = sym.inputs_note or params_str or "None"
|
|
50
|
+
outputs = sym.outputs_note or sym.return_type or "None"
|
|
51
|
+
fqdn_str = sym.fqdn or sym.name
|
|
52
|
+
lines.append(
|
|
53
|
+
f"- Function `{sym.name}` (FQDN: `{fqdn_str}`, "
|
|
54
|
+
f"File: `{callee_node.rel_path.name}`):"
|
|
55
|
+
)
|
|
56
|
+
lines.append(f" - Purpose: {purpose}")
|
|
57
|
+
lines.append(f" - Inputs: {inputs}")
|
|
58
|
+
lines.append(f" - Outputs: {outputs}")
|
|
59
|
+
if sym.overview:
|
|
60
|
+
first_lines = sym.overview.strip().splitlines()[:2]
|
|
61
|
+
summary_str = " / ".join(
|
|
62
|
+
line_item.strip() for line_item in first_lines
|
|
63
|
+
)
|
|
64
|
+
lines.append(f" - Summary: {summary_str}")
|
|
65
|
+
|
|
66
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Documentation label localization and JSON template management."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
DEFAULT_ENGLISH_LABELS: Dict[str, str] = {
|
|
8
|
+
"language": "English",
|
|
9
|
+
"doc_title_template": "{kind} Documentation: `{name}`",
|
|
10
|
+
"basic_info_title": "## 1. Basic Information",
|
|
11
|
+
"label_name": "Name",
|
|
12
|
+
"label_fqdn": "FQDN",
|
|
13
|
+
"label_symbol_kind": "Symbol Kind",
|
|
14
|
+
"label_location": "Location",
|
|
15
|
+
"location_template": "Line {start} to Line {end}",
|
|
16
|
+
"label_signature": "Signature / Type",
|
|
17
|
+
"parameters_title": "## 2. Parameters",
|
|
18
|
+
"label_none": "None",
|
|
19
|
+
"input_return_title": "## 3. Input / Return Specifications",
|
|
20
|
+
"label_input_constraints": "Input Constraints",
|
|
21
|
+
"label_return_type": "Return Type",
|
|
22
|
+
"label_return_value_side_effects": "Return Value & Side Effects",
|
|
23
|
+
"label_void_none": "void / None",
|
|
24
|
+
"called_functions_title": "## 4. Called Functions",
|
|
25
|
+
"referencing_functions_title": "## 4. Referencing Functions",
|
|
26
|
+
"source_code_snippet_title": "## Source Code Snippet",
|
|
27
|
+
"file_doc_title_template": "File Documentation: `{path}`",
|
|
28
|
+
"label_file_hash": "File SHA-256",
|
|
29
|
+
"label_detected_symbols": "Detected Symbols",
|
|
30
|
+
"symbol_list_title": "## Symbol List",
|
|
31
|
+
"symbol_list_item_template": "- `{name}` ({kind}) [Lines: {line_range}]",
|
|
32
|
+
"kind_function": "Function",
|
|
33
|
+
"kind_class": "Class",
|
|
34
|
+
"kind_method": "Method",
|
|
35
|
+
"kind_variable": "Variable",
|
|
36
|
+
"kind_struct": "Struct",
|
|
37
|
+
"kind_enum": "Enum",
|
|
38
|
+
"kind_interface": "Interface",
|
|
39
|
+
"kind_constant": "Constant",
|
|
40
|
+
"kind_type": "Type",
|
|
41
|
+
"kind_module": "Module",
|
|
42
|
+
"prose_referenced_by_template": (
|
|
43
|
+
"In the system architecture, it is referenced by {callers}."
|
|
44
|
+
),
|
|
45
|
+
"prose_root_entry_point": (
|
|
46
|
+
"It serves as the root entry point (system bootstrap and dispatcher)."
|
|
47
|
+
),
|
|
48
|
+
"prose_implementation_overview_template": (
|
|
49
|
+
"Implementation overview: {overview}"
|
|
50
|
+
),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_default_labels(language: str = "English") -> Dict[str, str]:
|
|
55
|
+
"""Return default dictionary of documentation labels (English base)."""
|
|
56
|
+
return dict(DEFAULT_ENGLISH_LABELS)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def translate_labels_with_llm(
|
|
60
|
+
llm_client: Any,
|
|
61
|
+
target_language: str,
|
|
62
|
+
) -> Dict[str, str]:
|
|
63
|
+
"""Translate documentation labels to target_language using LLM."""
|
|
64
|
+
from pystdoc.llm_client import extract_json_from_text
|
|
65
|
+
|
|
66
|
+
base_labels = dict(DEFAULT_ENGLISH_LABELS)
|
|
67
|
+
base_labels["language"] = target_language
|
|
68
|
+
|
|
69
|
+
system_prompt = (
|
|
70
|
+
"You are an expert technical documentation localizer. "
|
|
71
|
+
f"Translate software documentation labels and headings into natural, "
|
|
72
|
+
f"standard software engineering terms in {target_language}.\n"
|
|
73
|
+
"Guidelines:\n"
|
|
74
|
+
"- In Japanese software engineering, documentation is conventionally "
|
|
75
|
+
"called '仕様書' (e.g. '関数仕様書' for Function Documentation, "
|
|
76
|
+
"'クラス仕様書' for Class Documentation, "
|
|
77
|
+
"'ファイル仕様書' for File Documentation).\n"
|
|
78
|
+
"- When translating into Japanese, translate `doc_title_template` "
|
|
79
|
+
"as '{kind}仕様書: `{name}`', `file_doc_title_template` as "
|
|
80
|
+
"'ファイル仕様書: `{path}`', `kind_function` as '関数', "
|
|
81
|
+
"`kind_class` as 'クラス', `kind_method` as 'メソッド', "
|
|
82
|
+
"`kind_variable` as '変数', `kind_struct` as '構造体', "
|
|
83
|
+
"`kind_enum` as '列挙型', `kind_constant` as '定数'.\n"
|
|
84
|
+
"- For other languages, translate software terms idiomatic to that "
|
|
85
|
+
"language (e.g. Funktion in German, 函数 in Chinese).\n"
|
|
86
|
+
"- Keep placeholders {kind}, {name}, {path}, {start}, {end}, "
|
|
87
|
+
"{line_range}, {callers}, {overview} exactly as is.\n"
|
|
88
|
+
"- Respond strictly with a valid JSON object matching the input keys."
|
|
89
|
+
)
|
|
90
|
+
user_prompt = (
|
|
91
|
+
f"Translate the following JSON documentation labels into "
|
|
92
|
+
f"{target_language}:\n\n"
|
|
93
|
+
f"```json\n"
|
|
94
|
+
f"{json.dumps(base_labels, indent=2, ensure_ascii=False)}\n"
|
|
95
|
+
f"```\n\n"
|
|
96
|
+
f"Output ONLY valid JSON."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
messages = [
|
|
101
|
+
{"role": "system", "content": system_prompt},
|
|
102
|
+
{"role": "user", "content": user_prompt},
|
|
103
|
+
]
|
|
104
|
+
response_text = llm_client.chat_completion(messages)
|
|
105
|
+
parsed = extract_json_from_text(response_text)
|
|
106
|
+
if isinstance(parsed, dict) and len(parsed) >= 5:
|
|
107
|
+
merged = dict(base_labels)
|
|
108
|
+
for k, v in parsed.items():
|
|
109
|
+
if k in merged and isinstance(v, str) and v.strip():
|
|
110
|
+
merged[k] = v.strip()
|
|
111
|
+
merged["language"] = target_language
|
|
112
|
+
return merged
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
return base_labels
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_or_init_document_labels(
|
|
120
|
+
target_dir: Optional[Path],
|
|
121
|
+
language: str = "English",
|
|
122
|
+
llm_client: Optional[Any] = None,
|
|
123
|
+
) -> Dict[str, str]:
|
|
124
|
+
"""Load or initialize document_labels.json under target_dir/.pystdoc/."""
|
|
125
|
+
from pystdoc.doc_writer import normalize_language
|
|
126
|
+
|
|
127
|
+
norm_lang = normalize_language(language)
|
|
128
|
+
defaults = get_default_labels(norm_lang)
|
|
129
|
+
|
|
130
|
+
if target_dir is None:
|
|
131
|
+
return defaults
|
|
132
|
+
|
|
133
|
+
pystdoc_dir = target_dir / ".pystdoc"
|
|
134
|
+
labels_file = pystdoc_dir / "document_labels.json"
|
|
135
|
+
|
|
136
|
+
if labels_file.exists():
|
|
137
|
+
try:
|
|
138
|
+
content = labels_file.read_text(encoding="utf-8")
|
|
139
|
+
loaded = json.loads(content)
|
|
140
|
+
if (
|
|
141
|
+
isinstance(loaded, dict)
|
|
142
|
+
and loaded.get("language") == norm_lang
|
|
143
|
+
):
|
|
144
|
+
merged = dict(defaults)
|
|
145
|
+
merged.update(loaded)
|
|
146
|
+
return merged
|
|
147
|
+
except Exception:
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
# If language is not English, generate localized labels via LLM
|
|
151
|
+
final_labels = defaults
|
|
152
|
+
if norm_lang != "English" and llm_client is not None:
|
|
153
|
+
try:
|
|
154
|
+
print(
|
|
155
|
+
f"[Labels] Generating localized document labels for "
|
|
156
|
+
f"{norm_lang} via LLM...",
|
|
157
|
+
flush=True,
|
|
158
|
+
)
|
|
159
|
+
final_labels = translate_labels_with_llm(llm_client, norm_lang)
|
|
160
|
+
except Exception:
|
|
161
|
+
final_labels = defaults
|
|
162
|
+
|
|
163
|
+
# Save initial labels JSON for the target language
|
|
164
|
+
try:
|
|
165
|
+
pystdoc_dir.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
content = json.dumps(final_labels, indent=2, ensure_ascii=False) + "\n"
|
|
167
|
+
labels_file.write_text(content, encoding="utf-8")
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
return final_labels
|