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.
Files changed (31) hide show
  1. {pystdoc-0.3.0/src/pystdoc.egg-info → pystdoc-0.4.0}/PKG-INFO +1 -1
  2. {pystdoc-0.3.0 → pystdoc-0.4.0}/pyproject.toml +1 -1
  3. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/__init__.py +1 -1
  4. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/doc_writer.py +16 -8
  5. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/llm_client.py +85 -23
  6. {pystdoc-0.3.0 → pystdoc-0.4.0/src/pystdoc.egg-info}/PKG-INFO +1 -1
  7. {pystdoc-0.3.0 → pystdoc-0.4.0}/LICENSE +0 -0
  8. {pystdoc-0.3.0 → pystdoc-0.4.0}/README.md +0 -0
  9. {pystdoc-0.3.0 → pystdoc-0.4.0}/setup.cfg +0 -0
  10. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/cache.py +0 -0
  11. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/call_graph.py +0 -0
  12. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/cli.py +0 -0
  13. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/compilation_db.py +0 -0
  14. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/db.py +0 -0
  15. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/design_engine.py +0 -0
  16. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/engine.py +0 -0
  17. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/hasher.py +0 -0
  18. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/parser_clang.py +0 -0
  19. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/parser_generic.py +0 -0
  20. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/parser_python.py +0 -0
  21. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/report_engine.py +0 -0
  22. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/scanner.py +0 -0
  23. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc/symbols.py +0 -0
  24. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/SOURCES.txt +0 -0
  25. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/dependency_links.txt +0 -0
  26. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/entry_points.txt +0 -0
  27. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/requires.txt +0 -0
  28. {pystdoc-0.3.0 → pystdoc-0.4.0}/src/pystdoc.egg-info/top_level.txt +0 -0
  29. {pystdoc-0.3.0 → pystdoc-0.4.0}/tests/test_designgen.py +0 -0
  30. {pystdoc-0.3.0 → pystdoc-0.4.0}/tests/test_docgen.py +0 -0
  31. {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.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.3.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 = [
@@ -1,3 +1,3 @@
1
1
  """pystdoc: Python Structural & Topological Documentation Engine."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.4.0"
@@ -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 English representation (e.g. 'Japanese', 'English')."""
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 f"Executes `{sym.name}` operations.",
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=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=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=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=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=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 standard host/token/context/language options."""
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 values in {language}).
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 (all string values in {language}):
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=True, max_tokens=768, timeout=60)
231
- m = re.search(r"\{[\s\S]*\}", raw_res)
232
- if m:
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=True, max_tokens=768, timeout=60)
287
- m = re.search(r"\{[\s\S]*\}", raw_res)
288
- if m:
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.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