pystdoc 0.3.0__tar.gz → 0.4.0__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.3.0/src/pystdoc.egg-info → pystdoc-0.4.0}/PKG-INFO +1 -1
- {pystdoc-0.3.0 → pystdoc-0.4.0}/pyproject.toml +1 -1
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/__init__.py +1 -1
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/doc_writer.py +16 -8
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/llm_client.py +85 -23
- {pystdoc-0.3.0 → pystdoc-0.4.0/src/pystdoc.egg-info}/PKG-INFO +1 -1
- {pystdoc-0.3.0 → pystdoc-0.4.0}/LICENSE +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/README.md +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/setup.cfg +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/cache.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/call_graph.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/cli.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/compilation_db.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/db.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/design_engine.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/engine.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/hasher.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/parser_clang.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/parser_generic.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/parser_python.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/report_engine.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/scanner.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/symbols.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/SOURCES.txt +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/dependency_links.txt +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/entry_points.txt +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/requires.txt +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/top_level.txt +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/tests/test_designgen.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/tests/test_docgen.py +0 -0
- {pystdoc-0.3.0 → pystdoc-0.4.0}/tests/test_reportgen.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pystdoc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pystdoc"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
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 = [
|
|
@@ -14,11 +14,11 @@ from pystdoc.symbols import Symbol, get_kind_prefix
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def normalize_language(lang: Optional[str]) -> str:
|
|
17
|
-
"""Normalize language string into
|
|
17
|
+
"""Normalize language string into standard representation (e.g. 'Japanese', 'English')."""
|
|
18
18
|
if not lang:
|
|
19
19
|
return "English"
|
|
20
20
|
l_lower = lang.strip().lower()
|
|
21
|
-
if l_lower in ("japanese", "ja", "jp"):
|
|
21
|
+
if l_lower in ("japanese", "ja", "jp", "日本語"):
|
|
22
22
|
return "Japanese"
|
|
23
23
|
elif l_lower in ("english", "en"):
|
|
24
24
|
return "English"
|
|
@@ -78,6 +78,9 @@ def format_symbol_section(
|
|
|
78
78
|
language: str = "English",
|
|
79
79
|
) -> str:
|
|
80
80
|
"""Format a symbol's documentation into standardized Markdown string."""
|
|
81
|
+
norm_lang = normalize_language(language)
|
|
82
|
+
is_ja = norm_lang == "Japanese"
|
|
83
|
+
|
|
81
84
|
h_prefix = "#" * level
|
|
82
85
|
kind_display = sym.kind
|
|
83
86
|
if sym.fqdn:
|
|
@@ -86,11 +89,13 @@ def format_symbol_section(
|
|
|
86
89
|
snippet = get_code_snippet(file_path, sym.line_start, sym.line_end)
|
|
87
90
|
lang_id = get_code_language(file_path.suffix)
|
|
88
91
|
|
|
92
|
+
default_purpose = f"`{sym.name}` の処理を実行する。" if is_ja else f"Executes `{sym.name}` operations."
|
|
93
|
+
|
|
89
94
|
lines = [
|
|
90
95
|
f"{h_prefix} {sym.kind.capitalize()} Documentation: `{sym.name}`",
|
|
91
96
|
"",
|
|
92
97
|
"## 1. Design Intent & Purpose",
|
|
93
|
-
sym.purpose or
|
|
98
|
+
sym.purpose or default_purpose,
|
|
94
99
|
"",
|
|
95
100
|
"## 2. Basic Information",
|
|
96
101
|
f"- **Name**: `{sym.name}`",
|
|
@@ -132,7 +137,7 @@ def format_symbol_section(
|
|
|
132
137
|
if sym.children:
|
|
133
138
|
lines.append("")
|
|
134
139
|
for child in sym.children:
|
|
135
|
-
lines.append(format_symbol_section(child, file_path, level=level + 1, language=
|
|
140
|
+
lines.append(format_symbol_section(child, file_path, level=level + 1, language=norm_lang))
|
|
136
141
|
|
|
137
142
|
return "\n".join(lines)
|
|
138
143
|
|
|
@@ -145,6 +150,7 @@ def write_single_symbol_doc(
|
|
|
145
150
|
language: str = "English",
|
|
146
151
|
) -> Path:
|
|
147
152
|
"""Write an individual symbol document with immediate flush."""
|
|
153
|
+
norm_lang = normalize_language(language)
|
|
148
154
|
docgen_dir = target_dir / ".docgen" / "documents"
|
|
149
155
|
docgen_dir.mkdir(parents=True, exist_ok=True)
|
|
150
156
|
|
|
@@ -153,7 +159,7 @@ def write_single_symbol_doc(
|
|
|
153
159
|
out_file = docgen_dir / f"{rel_path.as_posix()}.{k_prefix}.{sym_id}.md"
|
|
154
160
|
|
|
155
161
|
full_path = target_dir / rel_path
|
|
156
|
-
content = format_symbol_section(symbol, full_path, level=1, language=
|
|
162
|
+
content = format_symbol_section(symbol, full_path, level=1, language=norm_lang)
|
|
157
163
|
|
|
158
164
|
write_flushed_text(out_file, content + "\n")
|
|
159
165
|
return out_file
|
|
@@ -167,10 +173,11 @@ def write_individual_symbol_docs(
|
|
|
167
173
|
language: str = "English",
|
|
168
174
|
) -> List[Path]:
|
|
169
175
|
"""Recursively write documentation files for each individual symbol with immediate flush."""
|
|
176
|
+
norm_lang = normalize_language(language)
|
|
170
177
|
created_files: List[Path] = []
|
|
171
178
|
|
|
172
179
|
for sym in symbols:
|
|
173
|
-
out_file = write_single_symbol_doc(target_dir, rel_path, sym, prefix_name=prefix_name, language=
|
|
180
|
+
out_file = write_single_symbol_doc(target_dir, rel_path, sym, prefix_name=prefix_name, language=norm_lang)
|
|
174
181
|
created_files.append(out_file)
|
|
175
182
|
|
|
176
183
|
if sym.children:
|
|
@@ -180,7 +187,7 @@ def write_individual_symbol_docs(
|
|
|
180
187
|
rel_path,
|
|
181
188
|
sym.children,
|
|
182
189
|
prefix_name=child_prefix,
|
|
183
|
-
language=
|
|
190
|
+
language=norm_lang,
|
|
184
191
|
)
|
|
185
192
|
created_files.extend(child_files)
|
|
186
193
|
|
|
@@ -195,6 +202,7 @@ def write_symbol_doc(
|
|
|
195
202
|
language: str = "English",
|
|
196
203
|
) -> Path:
|
|
197
204
|
"""Write comprehensive file documentation with immediate flush."""
|
|
205
|
+
norm_lang = normalize_language(language)
|
|
198
206
|
docgen_dir = target_dir / ".docgen" / "documents"
|
|
199
207
|
docgen_dir.mkdir(parents=True, exist_ok=True)
|
|
200
208
|
|
|
@@ -215,7 +223,7 @@ def write_symbol_doc(
|
|
|
215
223
|
|
|
216
224
|
lines.append("")
|
|
217
225
|
for sym in symbols:
|
|
218
|
-
lines.append(format_symbol_section(sym, full_path, level=2, language=
|
|
226
|
+
lines.append(format_symbol_section(sym, full_path, level=2, language=norm_lang))
|
|
219
227
|
lines.append("")
|
|
220
228
|
|
|
221
229
|
write_flushed_text(out_file, "\n".join(lines).strip() + "\n")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""LLM Client for LiteRT-LM, Ollama, and OpenAI-compatible endpoints with
|
|
1
|
+
"""LLM Client for LiteRT-LM, Ollama, and OpenAI-compatible endpoints with multi-language JSON normalization and retry resilience."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import os
|
|
@@ -31,6 +31,71 @@ def normalize_host_url(host: Optional[str]) -> str:
|
|
|
31
31
|
return h
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
def normalize_llm_json_dict(data: Dict[str, Any]) -> Dict[str, str]:
|
|
35
|
+
"""Normalize JSON dictionary keys from any language (English, Japanese, etc.) into standardized keys."""
|
|
36
|
+
normalized: Dict[str, str] = {}
|
|
37
|
+
|
|
38
|
+
for k in ("purpose", "目的", "設計意図", "意図", "description", "target", "role", "機能"):
|
|
39
|
+
if k in data and data[k]:
|
|
40
|
+
normalized["purpose"] = str(data[k]).strip()
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
for k in ("inputs", "入力", "引数", "パラメータ", "input", "arguments", "parameters"):
|
|
44
|
+
if k in data and data[k]:
|
|
45
|
+
normalized["inputs"] = str(data[k]).strip()
|
|
46
|
+
break
|
|
47
|
+
|
|
48
|
+
for k in ("outputs", "出力", "戻り値", "返り値", "output", "returns", "result"):
|
|
49
|
+
if k in data and data[k]:
|
|
50
|
+
normalized["outputs"] = str(data[k]).strip()
|
|
51
|
+
break
|
|
52
|
+
|
|
53
|
+
for k in ("overview", "概要", "要約", "説明", "summary", "details"):
|
|
54
|
+
if k in data and data[k]:
|
|
55
|
+
normalized["overview"] = str(data[k]).strip()
|
|
56
|
+
break
|
|
57
|
+
|
|
58
|
+
for k in ("significance", "重要性", "役割", "意義", "role"):
|
|
59
|
+
if k in data and data[k]:
|
|
60
|
+
normalized["significance"] = str(data[k]).strip()
|
|
61
|
+
break
|
|
62
|
+
|
|
63
|
+
for k in ("usage_scenario", "利用シナリオ", "使用シナリオ", "データフロー", "データ受け渡し", "scenario"):
|
|
64
|
+
if k in data and data[k]:
|
|
65
|
+
normalized["usage_scenario"] = str(data[k]).strip()
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
for k in ("top_down_summary", "要約", "概要", "全体設計要約", "summary"):
|
|
69
|
+
if k in data and data[k]:
|
|
70
|
+
normalized["top_down_summary"] = str(data[k]).strip()
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
return normalized
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def extract_json_from_text(raw_text: str) -> Dict[str, Any]:
|
|
77
|
+
"""Robustly extract and parse JSON object from LLM response text."""
|
|
78
|
+
clean = raw_text.strip()
|
|
79
|
+
# 1. Try markdown code block extraction
|
|
80
|
+
m_code = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", clean, re.IGNORECASE)
|
|
81
|
+
if m_code:
|
|
82
|
+
try:
|
|
83
|
+
return json.loads(m_code.group(1).strip())
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
# 2. Try outermost curly braces
|
|
88
|
+
m_brace = re.search(r"\{[\s\S]*\}", clean)
|
|
89
|
+
if m_brace:
|
|
90
|
+
try:
|
|
91
|
+
return json.loads(m_brace.group(0).strip())
|
|
92
|
+
except Exception:
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
# 3. Direct parse
|
|
96
|
+
return json.loads(clean)
|
|
97
|
+
|
|
98
|
+
|
|
34
99
|
class LLMClient:
|
|
35
100
|
"""OpenAI-compatible client with standard host, token, context size, and model configuration."""
|
|
36
101
|
|
|
@@ -59,7 +124,6 @@ class LLMClient:
|
|
|
59
124
|
if self._ping_url(self.base_url):
|
|
60
125
|
return True
|
|
61
126
|
|
|
62
|
-
# If the user explicitly passed a host/base_url, never overwrite it with default WSL fallback
|
|
63
127
|
if self.user_specified_host:
|
|
64
128
|
return False
|
|
65
129
|
|
|
@@ -191,7 +255,7 @@ class LLMClient:
|
|
|
191
255
|
) -> Dict[str, str]:
|
|
192
256
|
"""Generate concise explanations for a symbol in JSON format in the requested natural language."""
|
|
193
257
|
prompt = f"""Please analyze the following {lang} symbol `{name}` ({kind}) and output concise design intent and specifications in JSON format.
|
|
194
|
-
Output Language: {language} (Write all text
|
|
258
|
+
Output Language: {language} (Write all explanation text in {language}).
|
|
195
259
|
Be concise and avoid repetition.
|
|
196
260
|
|
|
197
261
|
### Signature / Type:
|
|
@@ -213,12 +277,12 @@ Be concise and avoid repetition.
|
|
|
213
277
|
{code}
|
|
214
278
|
```
|
|
215
279
|
|
|
216
|
-
Return ONLY a valid JSON object
|
|
280
|
+
Return ONLY a valid JSON object matching these keys (values written in {language}):
|
|
217
281
|
{{
|
|
218
|
-
"purpose": "Core design intent and purpose (1-2 concise sentences)",
|
|
282
|
+
"purpose": "Core design intent and purpose (1-2 concise sentences in {language})",
|
|
219
283
|
"inputs": "Input parameters description (or None)",
|
|
220
284
|
"outputs": "Return value or side effects description",
|
|
221
|
-
"overview": "Concise summary of implementation and role (1-3 sentences)"
|
|
285
|
+
"overview": "Concise summary of implementation and role (1-3 sentences in {language})"
|
|
222
286
|
}}
|
|
223
287
|
"""
|
|
224
288
|
messages = [
|
|
@@ -227,19 +291,18 @@ Return ONLY a valid JSON object (all string values in {language}):
|
|
|
227
291
|
]
|
|
228
292
|
|
|
229
293
|
try:
|
|
230
|
-
raw_res = self.chat_completion(messages, json_mode=
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return json.loads(m.group(0))
|
|
234
|
-
return json.loads(raw_res)
|
|
294
|
+
raw_res = self.chat_completion(messages, json_mode=False, max_tokens=768, timeout=60)
|
|
295
|
+
parsed = extract_json_from_text(raw_res)
|
|
296
|
+
return normalize_llm_json_dict(parsed)
|
|
235
297
|
except Exception as e:
|
|
236
298
|
if not allow_fallback:
|
|
237
299
|
raise LLMError(f"Failed to analyze symbol `{name}`: {e}") from e
|
|
300
|
+
is_ja = language in ("Japanese", "日本語")
|
|
238
301
|
return {
|
|
239
|
-
"purpose": f"Executes `{name}` operations.",
|
|
240
|
-
"inputs": "Accepts input parameters.",
|
|
241
|
-
"outputs": "Returns result value.",
|
|
242
|
-
"overview": f"Basic operation for `{name}`.",
|
|
302
|
+
"purpose": f"`{name}` の処理を実行する。" if is_ja else f"Executes `{name}` operations.",
|
|
303
|
+
"inputs": "パラメータを受け取る。" if is_ja else "Accepts input parameters.",
|
|
304
|
+
"outputs": "結果値を返す。" if is_ja else "Returns result value.",
|
|
305
|
+
"overview": f"`{name}` の基本処理。" if is_ja else f"Basic operation for `{name}`.",
|
|
243
306
|
}
|
|
244
307
|
|
|
245
308
|
def refine_variable_top_down(
|
|
@@ -283,16 +346,15 @@ Return ONLY a valid JSON object (all string values in {language}):
|
|
|
283
346
|
]
|
|
284
347
|
|
|
285
348
|
try:
|
|
286
|
-
raw_res = self.chat_completion(messages, json_mode=
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
return json.loads(m.group(0))
|
|
290
|
-
return json.loads(raw_res)
|
|
349
|
+
raw_res = self.chat_completion(messages, json_mode=False, max_tokens=768, timeout=60)
|
|
350
|
+
parsed = extract_json_from_text(raw_res)
|
|
351
|
+
return normalize_llm_json_dict(parsed)
|
|
291
352
|
except Exception as e:
|
|
292
353
|
if not allow_fallback:
|
|
293
354
|
raise LLMError(f"Failed top-down variable refinement for `{var_name}`: {e}") from e
|
|
355
|
+
is_ja = language in ("Japanese", "日本語")
|
|
294
356
|
return {
|
|
295
|
-
"significance": f"`{var_name}` is a state/data utilized by callers.",
|
|
296
|
-
"usage_scenario": "Passed and transformed across calling functions.",
|
|
297
|
-
"top_down_summary": f"Design summary for `{var_name}`.",
|
|
357
|
+
"significance": f"`{var_name}` は呼び出し元で使用される状態/データ。" if is_ja else f"`{var_name}` is a state/data utilized by callers.",
|
|
358
|
+
"usage_scenario": "呼び出し元関数間で受け渡される。" if is_ja else "Passed and transformed across calling functions.",
|
|
359
|
+
"top_down_summary": f"`{var_name}` の設計概要。" if is_ja else f"Design summary for `{var_name}`.",
|
|
298
360
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pystdoc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|